Understanding the IdentityServer and API communication - c#

From what I have researched, then it's best practice to seperate 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?

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.

Related

How to use [Authorize] to authenticate token returned by API?

guys!
Currently, I get a Bearer Token from an API.
What I want to do is:
Get token from external API
Use .NET [Authorize] in controller
Compare token returned from API with token informed in Authorization Header of request
If they are equal, authorize the request
If they are not equal, return 401
What's the best way of doing this?
After using [Authorize]attribute in your controller class; 3rd, 4th and 5th steps automatically performed by .NET built-in functions.
As far as I know, the asp.net core provide the build-in authentication schema to authenticate the JWT bear token.
You could put the right settings inside the registered service with the right secret and issuer settings, then your application will check the token and set the claims into your application to achieve the authentication.
More details about how it works, you could refer to this article.
If JTW is the only way you are authenticating endpoints, it should be as "simple" as;
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "..TODO..",
ValidateAudience = true,
ValidAudience = "..TODO..",
IssuerSigningKey = new SymmetricSecurityKey(..TODO..);
};
});

Retrieve claims from JWT authentication on ASP.NET Core MVC

I have inherited an ASP.NET Core 5 MVC application that has integrated the authentication of the users with a third-party identity server, WSO2.
The authentication is working fine, but I'm not able to understand how to retrieve the username of the user that has logged in.
Here is the code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddControllersWithViews();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ITokenService, TokenService>();
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
//auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddHttpClient();
}
in the controller I have added:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Administrator,Supervisor")]
Is the name and roles found in the JWT token?
If, so then the issue is that OpenIDConnect and Microsoft do not agree about what the name of the 'name' claim should be.
To address this, you need to tell AddJwtBearer, which claim is the 'name' and 'role' claim using:
.AddMyJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";

ASP.NET Core API Authentication with JWT breaks built-in authentication for website

I'm working on an ASP.NET Core project that has a website as well as an API. I need the API to be secured by some means, so I implemented JWT authentication. This worked as expected, and the API is only accessible by calls made with a valid token.
The problem is, the built-in login functionality on the website no longer works.
This is the code I added to Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
If I remove this code, the built-in login feature works as usual, but the API no longer works.
Is there a way I can make the JWT authentication only apply to the API controllers? Or is there a way I can make the built-in authentication use JWT?
I had the same problem and resolved it (using Asp.Net Core 3.0).
You can add more than one Authentication schema to your project with code like this.
// Read the token generation data...
var token = Configuration.GetSection("JwtTokenData").Get<JwtTokenData>();
services.AddAuthentication()
.AddCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
})
.AddJwtBearer(options => {
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
Notice how I haven't specified the default authentication schema when I call AddAuthentication. This allows the built-in Authentication to be the default for your pages or controller where you use the [Authorize] attribute.
But when you need to use JWT Authentication then you use this option on the Authorize attribute for your Web API controller or methods
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Ocelot Asp.net Core PreAuthentication Middleware

I'm currently using Ocelot as Api Gateway for a micro-services architecture. I have some authenticated reroutes and to be able to use it I declared a authentication Middleware like this :
var authenticationProviderKey = "Authentification";
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(authenticationProviderKey, x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = true,
ValidateAudience = true
};
});
I wanted to run some custom validation to implement refresh token workflow, to do so I implemented the preAuthentication Middleware to make so tests :
PreAuthenticationMiddleware = async (ctx, next) =>
{
IEnumerable<string> header;
ctx.DownstreamRequest.Headers.TryGetValues("Authorization", out header);
if (header.FirstOrDefault() != null)
{
if (JwtUtils.ValidateExpirationToken(header.FirstOrDefault()))
{
//On validate refresh token
Console.WriteLine("PreAuthentification Middleware");
Tuple<int, string> credentials = JwtUtils.retrieveInfos(header.FirstOrDefault());
string token = JwtUtils.GenerateToken(credentials.Item1, credentials.Item2);
ctx.DownstreamRequest.Headers.Remove("Authorization");
ctx.DownstreamRequest.Headers.Add("Authorization", token);
await next.Invoke();
}
}
}
From what I understood, when I make an api Call, the preAuthenticate Middleware would be called, and with next.Invoke() my Authentication middleware would be called. The newly generated token in my PreAuthentication middleware is a valid one, but my authentication middleware throws an expiredToken exception even tho he's not. Therefore I think the authentication Middleware is run against the first JWT when the new one has not been yet set to the Authorization Header. Is it the attended behaviour? Or maybe I did not understood correctly the middleware in Ocelot?
Anyway, some help would be much appreciated !
Have a good day,
Lio

Access userManager in api

I have an application in ASP.Net core divided into 3 projects (API, data, services). The API contains the controllers that will be calling the methods in the services project (that's where I'll be processing the data).
Right now I am using the Authentification system ASP provides, so I added those lines in the startup method.
services.AddIdentity<IdentityUser,IdentityRole>().AddEntityFrameworkStores<RHPDbContext>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "RHP User",
ValidIssuer = "MYIssuer",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MyRHPSuperKey"))
};
});
Since I am making an API I will be needing the token-based authentification
And also added this line in the configure method in startup
app.UseAuthentication();
My question now is how can I access the userManager in my services project so I can add users, or get the users, attribute roles to users...etc
Here is the way you can access userManager in your controller class.
public AccountController(UserManager<IdentityUser> userManager)

Categories