Search code examples
c#.netasp.net-identityidentityserver4

Understanding the IdentityServer and API communication


From what I have researched, then it's best practice to separate the API and IdentityServer into 2 projects (also 2 domains-i.e.: https://api.mywebsite.com and https://identity.mywebsite.com), so I did that.

In my IdentityServer/AuthenticationController.cs @ login-method it is currently making a JWT-token with a list of Claims(role(s), name, Jti (idk what that is), email, etc, that it puts into the JwtSecurityToken.

How does i.e. [Authorize(Roles = "Admin")] in my WebAPI/ProductController.cs communicate with my IdentityServer and then authorizing it. Is it in AddAuthentication(...); in Program.cs services? Or in AddJwtBearer(...)? Or specifically in ValidIssuer?

Where is the connection between API (i.e. https://api.mywebsite.com) and IdentityServer(https://identityserver.mywebsite.com)?

My IdentityServer-project Program.cs:

...
builder.Services.AddIdentityServer().AddAspNetIdentity<IdentityUser>().AddClientStore<InMemoryClientStore>().AddResourceStore<InMemoryResourcesStore>();

builder.Services.AddAuthentication(auth =>
{
    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        
        ValidAudiences = builder.Configuration.GetSection("JWT:ValidAudiences").Get<string[]>(),
        ValidIssuer = builder.Configuration["JWT:ValidIssuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"]))
    };
}); 
...

My API-project Program.cs:

...
builder.Services.AddAuthentication(auth =>
{
    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,

        ValidAudiences = builder.Configuration.GetSection("JWT:ValidAudiences").Get<string[]>(),
        ValidIssuer = builder.Configuration["JWT:ValidIssuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"]))
    };
});
...

Also, are my both codeblocks well-written when it comes to IdentityServer and API authentication and authorization?


Solution

  • AddJwtBearer in the API is in charge of taking the incoming access token and converting it to a "ClaimsPrincipal" user. Then that user is passed with all its claim to the authorization handler that will determine if the user is allowed to pass or not.

    For example the [Authorize(Roles = "Admin")] attribute will check the role claims in the user if it contains the Admin role claim or not.

    Usually you don't need to specify the IssuerSigningKey in AddJwtBearer and instead let AddJwtBearer to automatically retrieve it from IdentityServer directly at startup. (just remove IssuerSigningKey). You typically add IssuerSigningKey if the API can't talk to IdentityServer.

    To complement this answer, I wrote a blog post that goes into more detail about this topic: Troubleshooting JwtBearer authentication problems in ASP.NET Core