Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
862 views
in Technique[技术] by (71.8m points)

entity framework core - Identity Server 4 Claims empty on API

I have been trying to Integrate Identity Server 4 with SPA application. I am able to Authorize the Application in API but after the authorization the User.Claims are always empty though i have added the Claims in Scopes.

I am using Asp.net Identity in API with entity framework core.

My project are distributed in different projects.

  1. Project.Auth (using Identity Server 4)
  2. Project.Admin
  3. Project.Data (where my Context and Migration Lies)
  4. Project.Domain(Enities)
  5. Project.Service(Repository and ViewModel)

Startup.cs For Project.Admin

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<MyContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddAuthorization();

        services.AddIdentity<User, IdentityRole<Guid>>()
            .AddEntityFrameworkStores<MyContext>()
            .AddDefaultTokenProviders();

        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "https://localhost:44305";
                options.RequireHttpsMetadata = false;
                options.ApiName = "api1";
            });

        services.AddCors(options =>
        {
            options.AddPolicy("default", policy =>
            {
                policy.WithOrigins("http://localhost:8080")
                    .AllowAnyHeader()
                    .AllowAnyMethod();
            });
        });

        services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
        services.AddScoped<IContractService, ContractService>();
        services.AddScoped<IClientService, ClientService>();

        services.AddAutoMapper(mapperConfig => mapperConfig.AddProfiles(GetType().Assembly));


        services.AddMvcCore()
            .AddJsonFormatters();
    }

Identity Server Setup

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = builder =>
            builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                sql => sql.MigrationsAssembly(typeof(MyContext).GetTypeInfo().Assembly.GetName().Name));
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = builder =>
            builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                sql => sql.MigrationsAssembly(typeof(MyContext).GetTypeInfo().Assembly.GetName().Name));
    }).AddAspNetIdentity<User>();

TestUser.cs

public class TestUsers
{
    public static List<TestUser> Users = new List<TestUser>
    {
        new TestUser{SubjectId = Guid.NewGuid().ToString(), Username = "alice", Password = "alice",
            Claims =
            {
                new Claim(JwtClaimTypes.Name, "Alice Smith"),
                new Claim(JwtClaimTypes.Role,"Admin"),
                new Claim(JwtClaimTypes.GivenName, "Alice"),
                new Claim(JwtClaimTypes.FamilyName, "Smith"),
                new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
                new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
            }
        }
    };
}

Client

new Client
{
    ClientId = "js",
    ClientName = "JavaScript Client",
    AllowedGrantTypes = GrantTypes.Implicit,
    AllowAccessTokensViaBrowser = true,
    AlwaysIncludeUserClaimsInIdToken = true,
    RedirectUris =            new List<string> {"http://localhost:8080/silent","http://localhost:8080/authredirect"},
    PostLogoutRedirectUris =   { "http://localhost:8080" },
    AllowedCorsOrigins =     { "http://localhost:8080" },

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "api1",
        "role"
    }
}

ApiResource

new ApiResource("api1", "My API")

IdentityResources

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource> {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Email(),
        new IdentityResource {
            Name = "role",
            UserClaims = new List<string> {"role"}
        }
    };
}

Decode Token

{
  "nbf": 1525602392,
  "exp": 1525605992,
  "iss": "https://localhost:44305",
  "aud": [
    "https://localhost:44305/resources",
    "api1"
  ],
  "client_id": "js",
  "sub": "c81ce899-77d9-4c34-ab31-b456129ee762",
  "auth_time": 1525601959,
  "idp": "local",
  "scope": [
    "openid",
    "profile",
    "role",
    "api1"
  ],
  "amr": [
    "pwd"
  ]
}

Why the API is able to authorize and authenticate the Request but no Details on User and Claims? Did i missed anything on the API startup class? or there is some misconfiguration on the precedence on the startup class.

The Claims and User used to have the value before i added the DI for Context and Services on the Startup Class.

I tried again by removing the references to Project.Service and removing every thing from the Statrup class in Project.Admin. I was able to get the Claim information. As shown below.

Claim Information when Removed Dependency from Start Up

However when i add the DI to Context and other services. My Claim info got lost. However i am still authenticated and it is passing my Authorize Filter.

Break Point on <code>User.Claims</code>

Edited: When i was checking the log on my application i found a error

"Identity.Application" was not authenticated. Failure message: "Unprotect ticket failed"

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I have found my Solution for this problem. I was missing couple of things on my code:

  1. There was the Duplicate references to IdentityServer4.AccessTokenValidation.
  2. I was missing the DefaultChallengeScheme on my API ConfigureServices

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "oidc";
    })
    .AddIdentityServerAuthentication(options =>
    {
      options.Authority = "https://localhost:44305";
      options.RequireHttpsMetadata = false;
      options.ApiName = "api1";
    });
    

So my Configure Service became like below:

 public void ConfigureServices(IServiceCollection services)
{

    services.AddMvcCore().AddAuthorization().AddJsonFormatters();

    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));
    services.AddIdentity<User, IdentityRole<Guid>>().AddEntityFrameworkStores<MyContext>().AddDefaultTokenProviders();


    services.AddAuthentication(
        options =>
        {
            options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        })
    .AddIdentityServerAuthentication(options =>
    {
        options.Authority = "https://localhost:44305";
        options.RequireHttpsMetadata = false;
        options.ApiName = "api1";

    });

    services.AddCors(options =>
    {
        // this defines a CORS policy called "default"
        options.AddPolicy("default", policy =>
        {
            policy.WithOrigins("http://localhost:8080")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
    });
    services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
    services.AddScoped<IContractService, ContractService>();
    services.AddScoped<IClientService, ClientService>();

    services.AddAutoMapper(mapperConfig => mapperConfig.AddProfiles(GetType().Assembly));

}

Changing above two things solved my problem for missing claims and Authorized without the Bearer Token.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...