JWTs can be used as OAuth 2.0 Bearer Tokens to encode all relevant parts of an access token into the access token itself instead of having to store them in a database.
You can use a JWT as a Bearer token, but since it's only base64 encoded, you can pull out that payload data. A truly opaque Bearer token will be meaningless to anything other than your server. A bearer token is opaque. It could be a JWT, it could be something else, depending on the application.
Yes Json web token(jwt) is enough! But be mindful of which data you send through the token because it can be decoded. The only thing that make jwt secure is the signature.
AuthenticationScheme is the name of the scheme to use by default when a specific scheme isn't requested. If multiple schemes are used, authorization policies (or authorization attributes) can specify the authentication scheme (or schemes) they depend on to authenticate the user.
You can totally achieve what you want:
services
.AddAuthentication()
.AddJwtBearer("Firebase", options =>
{
options.Authority = "https://securetoken.google.com/my-firebase-project"
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "my-firebase-project"
ValidateAudience = true,
ValidAudience = "my-firebase-project"
ValidateLifetime = true
};
})
.AddJwtBearer("Custom", options =>
{
// Configuration for your custom
// JWT tokens here
});
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase", "Custom")
.Build();
});
Let's go through the differences between your code and that one.
AddAuthentication
has no parameterIf you set a default authentication scheme, then on every single request the authentication middleware will try to run the authentication handler associated with the default authentication scheme. Since we now have two possible authentication schemes, there's no point in running one of them.
AddJwtBearer
Every single AddXXX
method to add an authentication has several overloads:
Now, because you use the same authentication method twice but authentication schemes must be unique, you need to use the second overload.
Since the requests won't be authenticated automatically anymore, putting [Authorize]
attributes on some actions will result in the requests being rejected and an HTTP 401
will be issued.
Since that's not what we want because we want to give the authentication handlers a chance to authenticate the request, we change the default policy of the authorization system by indicating both the Firebase
and Custom
authentication schemes should be tried to authenticate the request.
That doesn't prevent you from being more restrictive on some actions; the [Authorize]
attribute has an AuthenticationSchemes
property that allows you to override which authentication schemes are valid.
If you have more complex scenarios, you can make use of policy-based authorization. I find the official documentation is great.
Let's imagine some actions are only available to JWT tokens issued by Firebase and must have a claim with a specific value; you could do it this way:
// Authentication code omitted for brevity
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase", "Custom")
.Build();
options.AddPolicy("FirebaseAdministrators", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("Firebase")
.RequireClaim("role", "admin")
.Build());
});
You could then use [Authorize(Policy = "FirebaseAdministrators")]
on some actions.
A final point to note: If you are catching AuthenticationFailed
events and using anything but the first AddJwtBearer
policy, you may see IDX10501: Signature validation failed. Unable to match key...
This is caused by the system checking each AddJwtBearer
in turn until it gets a match. The error can usually be ignored.
This is an extension of Mickaël Derriey's answer.
Our app has a custom authorization requirement that we resolve from an internal source. We were using Auth0 but are switching to Microsoft Account authentication using OpenID. Here is the slightly edited code from our ASP.Net Core 2.1 Startup. For future readers, this works as of this writing for the versions specified. The caller uses the id_token from OpenID on incoming requests passed as a Bearer token. Hope it helps someone else trying to do an identity authority conversion as much as this question and answer helped me.
const string Auth0 = nameof(Auth0);
const string MsaOpenId = nameof(MsaOpenId);
string domain = "https://myAuth0App.auth0.com/";
services.AddAuthentication()
.AddJwtBearer(Auth0, options =>
{
options.Authority = domain;
options.Audience = "https://myAuth0Audience.com";
})
.AddJwtBearer(MsaOpenId, options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "00000000-0000-0000-0000-000000000000",
ValidateIssuer = true,
ValidIssuer = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidateLifetime = true,
RequireSignedTokens = true,
ClockSkew = TimeSpan.FromMinutes(10),
};
options.MetadataAddress = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0/.well-known/openid-configuration";
}
);
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes( Auth0, MsaOpenId )
.Build();
var approvedPolicyBuilder = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(Auth0, MsaOpenId)
;
approvedPolicyBuilder.Requirements.Add(new HasApprovedRequirement(domain));
options.AddPolicy("approved", approvedPolicyBuilder.Build());
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With