How to set custom token provider for cookies in ASP.NET Core Identity 3.1? - asp.net-core

tl;dr: Is there way how to set custom TokenProvider or something similar e.g. (jwt tokens has IssuerSigningKey) to cookies?
I have a .Net Core 3.1 backend with identity Authentication. I had a problem with confirming generated email token.. “Invalid Token” error. From this SO answer I found out there can be problem with my hosting. I have shared hosting for my application and my application was often restarted. I believe this was causing my problems. So as answer suggested I created my own TokenProvider:
services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<MyContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<AesDataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider);
This helps. For authentication I was using JwtBearer tokens with custom IssuerSigningKey
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
})
I found out that SignInManager from Identity is creating Cookies by default. So I tough it will be better to use this cookies instead of my jwt tokens saved in localstorage. So I set up expiration time for my cookies to 30 days.
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.ExpireTimeSpan = TimeSpan.FromDays(30);
});
Everything worked great.. until I hosted my application to server. I believe that when my app is restarted, cookie validation fail and I am getting 401 from server. So..finally to my question.. Is there way how to set custom TokenProvider or something similar e.g. (jwt tokens has IssuerSigningKey) to cookies?

There are no existing API (class, method) for you customize cookies in ASP.NET Core Identity.
You can build yourself without ASP.NET Core Identity: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1
Reference: https://github.com/dotnet/aspnetcore/blob/master/src/Http/Http.Abstractions/src/CookieBuilder.cs
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookiebuilder?view=aspnetcore-3.0
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions?view=aspnetcore-3.0

Related

How to use ASP.NET Core Identity without IdentityServer4?

We are starting a new web application that will be hosted in our customers infrastructure. Since this is a solution that will be here for a while and serve as a base for a lot of future products, we wanted to have a future proof security, that would be future poff (SSO / MFA) but this is something for like in 3 years. It's important for our customer that we rely on some standards, so I thought about using OpenId.
The solution will be based on ASP.NET Core + Angular. So I found out there was ASP.NET Core Identity, already compatible with OpenID Connect, but then I saw here that Microsoft recommends Duende Identity Server (IdentityServer4).
The problem is that we are a small team, building a small application, but in a big enterprise, so will have to go for the licensed version. The other problem is that since its our customers that deploy themself the application, we do not control how many servers will be deployed, therefore we would have to opt for an "enterprise" subscription, which is totally out of our budget.
Despite this, we were hoping that we could still use ASP.NET Core Identity to connect to different sources of users, manage permissions for our app, use the attributes on our controllers.
So, how to use ASP.NET Core Identity, without using IdentityServer?
According to the MSFT docs
ASP.NET Core Identity adds user interface (UI) login functionality to
ASP.NET Core web apps.To secure web APIs and SPAs, use one of the
following:
Azure Active Directory Azure
Active Directory B2C (Azure AD B2C)
IdentityServer4
So they first offer their cloud solutions.Identityserver4 free version is still supported though till the .Net Core 3.1 EOL.
As a free-free option without any predefined EOL, you can try this OpenIddict sample as a start point for your solution, however it has a bit more gaps to be filled in yourself.
And here is an explanation why MSFT don't offer it in their docs (spoiler: see above)
You can use pure ASP.NET Core without IdentityServer.
It's quite easy if you're using the same backend for authentication and API.
Example (copied from source):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/security/getMessage", () => "Hello World!").RequireAuthorization();
app.MapPost("/security/createToken",
[AllowAnonymous] (User user) =>
{
if (user.UserName == "joydip" && user.Password == "joydip123")
{
var issuer = builder.Configuration["Jwt:Issuer"];
var audience = builder.Configuration["Jwt:Audience"];
var key = Encoding.ASCII.GetBytes
(builder.Configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials
(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);
var stringToken = tokenHandler.WriteToken(token);
return Results.Ok(stringToken);
}
return Results.Unauthorized();
});
app.UseAuthentication();
app.UseAuthorization();
app.Run();
See also:
Similar thread

AutomaticAuthenticate for ASP.NET IdentityServer4 on open routes?

I have a few endpoints in an ASP.NET core application that's also hosting IdentityServer.
When I issue a JWT token to a client, and that client calls an [AllowAnonymous] endpoint with the JWT as a bearer token, the User principal is empty. It looks like the token isn't being parsed by any middleware, and there doesn't seem to be an option to specify always attempting to parse a JWT.
Is there a way to get this handled automatically or do I need to use the .AddJwtBearer extension? If the latter I can't seem to find an easy way for me to populate the signingkey such that it matches the ones configured for identity server.
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = ?? signing keys from Identity Server,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = // I guess this is typically the public URL for this instance,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
I would guess that for the SigningKey I could pass through the X509 that I'm configuring on IdentityServer via the x509SecurityKey class
UseJwtBearerAuthentication is obsolete. Configure JwtBearer authentication with AddAuthentication().AddJwtBearer in ConfigureServices.
Also make sure app.UseAuthentication(); is added in your startup class.
I find a best practice is to always host IdentityServer, client and API on separate ASP.NET Core instances, because otherwise its hard to reason about what is what. IdentityServer issues its own User and cookie so I think your system is confused about what user to use?
Reference:
JwtBearerAppBuilderExtensions.UseJwtBearerAuthentication Method
Auth 2.0 Migration announcement

Azure b2c unauthorized when making api calls in .net5 web api

I am facing an issue authorizing client apps (users) with azure B2C.
On the backend I have an asp.net5 web api. As for the front-end I am using angular client.
I have registered both apps in my B2c tenants. I've added API Premissions on both apps, also granted admin consents.
Now, when I run the user flow (from the azure portal) and specify the web api in the form, the token works fine, I can make api calls and I get status 200.
However, when tokens are retrieved upon the client app (angular), I get 401 unauthorized response.
My authentication Midleware is configured as follows:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtConfig =>
{
jwtConfig.Audience = Configuration["AzureAdB2C:ClientId"];
jwtConfig.Authority = $"{Configuration["AzureAdB2C:Instance"]}/tfp/{Configuration["AzureAdB2C:Domain"]}/{Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/v2.0";
jwtConfig.RequireHttpsMetadata = false;
jwtConfig.SaveToken = true;
jwtConfig.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = jwtConfig.Audience,
ValidIssuer = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/v2.0/"
};
});
Anyone knows what could the problem be?
Solution:
I made some research, and altered the code a little bit, to get more information on what is happening, so I found out that the problem was at the scopes. I was specifying wrong scope name at the client app, therefore I was getting 401 unauthorized.

Generate JWT token on successful authentication with Windows Authentication

I'm having some difficulty understanding how to use Windows Authentication to authenticate a user, but then return a JWT to the client containing that authenticated user's claims. This is using .Net Core 2.0.
I've put the following in Startup.cs.
services.AddAuthentication
(
IISDefaults.AuthenticationScheme
).AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("someKey")),
ValidateLifetime = true,
};
}
);
However, it doesn't seem that this is sufficient to generate the token. Some other guidance I've read suggests the token should be generate manually as part of an authorization routine but this seems to be tailored for when validating a username/password against a database or other provider. (For instance: https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login.) But with windows auth there does not appear to be an event or other structure that allows me to do this.
Any ideas?

.net Core Api authentication with ADFS 2012

I need to configure my .Net Core Web Api (.Net Framework) to use ADFS 3.0 (2012) to validate the Bearer tokens sent by our mobile clients.
I am able to generate the access_token from the ADFS server, and I pass it in the Authorization header.
My problem is in the API: how do I configure it to validate and autorize the user?
I searched in many places and I could not find a definitive method of doing it.
What I tried so far:
Used IdentityServer4 (Failed because it uses JWT and ADFS doesn't offer OpenID
Tried UseOpenIdConnectAuthentication (found example at IdentityServer4)
a custom Middleware
I can't use another method, I need to support oAuth2.
So, how do I do it?
Here is my latest try:
var connectOptions = new OpenIdConnectOptions
{
AuthenticationScheme = "adfs",
SignInScheme = "idsrv.external", //IdentityServerConstants.ExternalCookieAuthenticationScheme,
SignOutScheme = "idsrv", //IdentityServerConstants.SignoutScheme,
AutomaticChallenge = false,
DisplayName = "ADFS",
Authority = $"https://{options.AdfsHostName}/adfs/oauth2",
ClientId = options.ClientID,
ResponseType = "id_token",
Scope = { "openid profile" },
CallbackPath = new PathString("/signin-adfs"),
SignedOutCallbackPath = new PathString("/signout-callback-adfs"),
RemoteSignOutPath = new PathString("/signout-adfs"),
ClaimsIssuer = $"https://{options.AdfsHostName}/adfs/services/trust",
//TokenValidationParameters = new TokenValidationParameters
//{
// ValidateIssuer = true,
// ValidIssuer = $"https://{options.AdfsHostName}/adfs/services/trust"
//},
};
app.UseOpenIdConnectAuthentication(connectOptions);
I get a very quick 401 on every calls, with a valid token. In fact, while I see the connection in the console window, I don't see any other log in the Roslyn console window regarding the security validation.
I'm currently using ASP.Net Core 1.1.X, and if I can I'd avoid moving to .Net Core 2.0, as we are late in the project and it contains many breaking changes...
Feel free to ask for more info, and I'll appreciate all the good advices!
As it turns out, we can use the JwtBearerAuthentication with ADFS 3.0.
My initial problem with it was that it went to fetch the metadata at /.well-known/openid-configuration, but ADFS 3.0 does not support OpenID and this returns a 404.
I read in another post (I'll update it when I find it) that if with the right configuration, it won't need to fetch the config. But what configuration?
Well I found deep in the (MS) code that if one pass an OpenIdConnectConfiguration object to the Configuration property of the JwtBearerOptions, it wont fetch the metadata.
So here is my code now:
var rawCertData = Convert.FromBase64String(options.X509SigninCertificate);
X509Certificate2 cert = new X509Certificate2(rawCertData);
SecurityKey signingKey = new X509SecurityKey(cert);
The X509 cert data comes from the supported adfs metadata at this url
https://Your.ADFS.Site/FederationMetadata/2007-06/FederationMetadata.xml
It contains this:
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>SOMEUUENCDODEDSTRING=</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
I simply copied the UUEncoded string in my settings' X509SigninCertificate property.
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = $"https://{options.AdfsHostName}/adfs/services/trust",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = options.ClientUri, //"https://YOUR-AUDIENCE/",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
var connectOptions = new OpenIdConnectConfiguration
{
Issuer = $"https://{options.AdfsHostName}/adfs/services/trust",
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
Configuration = connectOptions
});
The important line here is
Configuration = connectOptions
By doing this, you tell the validator to not fetch the metadata. Simple as that.
I was able to validate my token (AUD, ISS and SIGN) and I can use ADFS in my project.
Only ADFS 2016 supports OpenID Connect. If you want to use the OAuth endpoint in 2012, you need to write your own authorisation handler. An example to build on would be ASP.NET Core's own Twitter implementation. Note that these handlers need to be implemented differently in ASP.NET Core 1.* vs 2.0+.