Microsoft JWT token validation - c#

I receive a JWT token (ID token) how do I validate it correctly? As I understand it, Microsoft uses an asynchronous signature for the JWT token, so I have two questions: Where can I get the public key for verification? And secondly, how can I use it for verification?
P.S. why does google have an official token validation library, but microsoft does not?
I need to verify tokenId token received from MS Azure AD
The token itself, I get on the Angular client, which is then sent to the server, where it must be verified.
Below is an example of code that displays tokenId to the console.
export class AccountComponent implements OnInit {
constructor(private tokenService: TokenStorageService, private readonly _authService: SocialAuthService) { }
ngOnInit(): void {
this._authService.authState.subscribe(this.externalAccountLogin);
}
signInWithMicrosoft(){
this._authService.signIn(MicrosoftLoginProvider.PROVIDER_ID);
}
externalAccountLogin(user: SocialUser): void{
switch(user.provider){
case MicrosoftLoginProvider.PROVIDER_ID:
console.log(user.idToken);
break;
case GoogleLoginProvider.PROVIDER_ID:
//ToDo
break;
}
}
}

It is very common and there are lot of articles to implement this. Please find the below
JWT Implementation in Asp.Net Core
JWT Token Implementation .Net 6.0
Creation of JWT Token .Net
Microsoft also has library to validate jwt token.
How JWT Validate Token
There are three major part in tokens :- Token Type & Algo, Payload and Last one Signature.
When you add Jwt Configuartion in your applicationof .Net Core then
Validate Token
First it check that signature is correct or not. if someone tamper the
signature then throw exception for token is not valid.
For this we provide validation parameter such as key, issuer and audience in TokenValidationParameters
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
var Key = Encoding.UTF8.GetBytes(Configuration["JWT:Key"]);
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JWT:Issuer"],
ValidAudience = Configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Key)
};
});
Need to valid payload or claim
We can create middleware to validate payload or claim. And Validate by fetching data from payload and create logic validate that.
Validate JWT Token of Azure AD
A. Can Use Microsoft Graph API to validate that.
Enable ASP.NET Core web app to sign in users and call Microsoft Graph
Protect Aspnet Core Application
B. OR Follow the same Steps which mentioned above by using TokenValidationParameters. Key, issuer and audience are also available in JWT TokenId of Azure AD. Get from azure portal and copy in startup in TokenValidationParameters.

why does google have an official token validation library, but microsoft does not?
Have you try microsoft.identity.web package.
https://learn.microsoft.com/en-us/azure/active-directory/develop/microsoft-identity-web
I could authenticate my API with it.

Related

JWT authentication returns 401 when policy is set in ASP.NET Core

I am trying to add multiple ways to authenticate users in our app. All but one are working flawlessly. I am able to log in with ASP.NET Core Identity, GitHub, Azure AD, and even API auth, but JWT is giving me a bit of a headache as I always get a 401 response when I pass in an bearer token to the authorization header.
This might have something to do with a custom middleware class that works on this header:
app.UseMiddleware<JwtAuthMiddleware>()
.UseAuthentication()
.UseAuthorization()
The middleware class in question:
public class JwtAuthMiddleware
{
private readonly RequestDelegate _next;
public JwtAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null)
{
string jwtEncodedString = authHeader[7..]; // Fetch token without Bearer
JwtSecurityToken token = new(jwtEncodedString: jwtEncodedString);
ClaimsIdentity identity = new(token.Claims, "jwt");
context.User = new ClaimsPrincipal(identity);
}
return _next(context);
}
}
The identity setup is fairly simple.
services
.AddAuthentication()
.AddCookie(/*config*/)
.AddGitHub(/*config*/)
.AddJwtBearer(/*config*/)
.AddAzureAd(/*config*/);
string[] authSchemes = new string[]
{
IdentityConstants.ApplicationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
GitHubAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme,
"InHeader"
};
AuthorizationPolicy authnPolicy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.FallbackPolicy = authnPolicy;
});
I also set a filter in AddMvc:
.AddMvc(options =>
{
AuthorizationPolicy policy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
JWT works when options.FallbackPolicy is not set. Otherwise, I get a 401 response. Inspecting the DenyAnonymousAuthorizationRequirement in debug mode confirms this as there is no principal set with the right claims filled out. So it looks like context.User is ignored or reset.
Ideally, I would want to get rid of JwtAuthMiddleware altogether, but I still have to figure out how to combine JWT with cookie authentication in this particular setup. Any thoughts?
You don't need to use this custom JwtAuthMiddleware and define the custom ClaimsPrincipal like that. The problem is that you are just extracting the claims from the token. Setting the ClaimsPrincipal like that wouldn't automatically authenticate the user i.e. Context.User.Identity.IsAuthenticated would be false. Hence you get 401.
The token needs to be validated. You are already using AddJwtBearer which you can customize the code like below if you haven't done that.
JWT bearer authentication performs authentication automatically by extracting and validating a JWT token from the Authorization request header. However, you need to set the TokenValidationParameters which tells the handler what to validate.
services
.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
// you don't have to validate all of the parameters below
// but whatever you need to, but see the documentation for more details
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.JwtToken.Issuer, // presuming config is your appsettings
ValidateAudience = true,
ValidAudience = config.JwtToken.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))
};
})
However, there is a caveat. When combining multiple authentication mechanisms, you need to utilise multiple authentication schemes. They play a role in defining the logic to select the correct authentication mechanism. That is because you can call AddJwtBearer or AddCookie multiple times for different scenarios. If the scheme name is not passed, it would try to utilize the default scheme and the authentication will fail, as it would have no way of knowing which mechanism to use JWT or AzureAd as they both use the Bearer token.
You mentioned that JWT works when FallbackPolicy is not set but you get 401 otherwise. That is because your authorization policy requires a user to be authenticated. The user was never authenticated as I mentioned in the beginning. If FallbackPolicy is not set it would work in the context of JWT being passed in the alright, but there is no requirement to check e.g user is authenticated and has a specific claim or role, so it works.
You would need to define an authentication policy scheme and set the ForwardDefaultSelector property of the PolicySchemeOptions. ForwardDefaultSecltor is used to select a default scheme for the current request that authentication handlers should forward all authentication operations to by default.
So basically you need to set the ForwardDefaultSecltor delegate which uses some logic to forward the request to the correct scheme to handle the authentication.
So the above code would change to:
// scheme names below can be any string you like
services
.AddAuthentication("MultiAuth") // virtual scheme that has logic to delegate
.AddGitHub(GitHubAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses GitHub scheme
.AddAzureAd("AzureAd", /*config*/) // uses AzureAd scheme
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses Cookies scheme
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { // above code for AddJwtBearer }) // uses Bearer scheme
// this is quite important to use the same virtual scheme name below
// which was used in the authentication call.
.AddPolicyScheme("MultiAuth", null, options =>
{
// runs on each request should return the exact scheme name defined above
options.ForwardDefaultSelector = context =>
{
// if the authorization header is present, use the Bearer scheme
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
{
return "Bearer";
}
// custom logic for GitHub
...
return GitHubAuthenticationDefaults.AuthenticationScheme;
// custom logic for AzureAd
...
return "AzureAd";
// otherwise fallback to Cookies or whichever is the default authentication scheme
return CookieAuthenticationDefaults.AuthenticationScheme;
};
});
That way, you just remove the JwtAuthMiddleware. AddJwtBearer would automatically add the claims in the Claims object when the token is validated, which you can then use to define custom authorization policies to authorize the user.
I hope that helps.
#Shazad Hassan made some good points in his answer. The custom middleware was in fact not necessary in the end because the System.IdentityModel.Tokens.XXXX assemblies were throwing errors that the tokens were invalid. I never really knew the token was invalid because the middleware assumed it was correct (and content-wise, it was), while all this time the context was running for an unauthenticated user, which became apparent when I applied the fallback policy.
So I rewrote the token generation code and now I no longer get this issue, and I can safely remove the custom middleware. So far so good.
While I still have to bone up on authentication schemes and all that, for the moment it doesn't even seem necessary to have multiple calls to AddAuthentication because this works:
services
.AddAuthentication()
.AddCookie(configureCookieAuthOptions)
.AddGitHub(configureGithub)
.AddAzureAd(config)
.AddJwtBearer(configureJwtBearerOptions)
.AddApiKeyInHeader<ApiKeyProvider>("InHeader", options =>
{
options.Realm = "My realm";
options.KeyName = "X-API-KEY";
});
With this, I am able to log in with every method in the list. Looks promising, but might need to be reviewed by someone who knows the ins and outs of authentication in ASP.NET Core.
So: some clients will be sending a cookie, and some will be sending Authorize header with a bearer token.
These are authentication schemes that you set up with AddAuthentication.
After that you need to set up authorization (AddAuthorization) to grant access to users who are authentication with either of these schemes.
(Authentication means checking that you are who you say you are (users), authorization means what you are and aren't allowed to do (roles) -- it's confusing.)

Get claims from jwt token into context.User.Claims

I'm trying to get claims from the JWT token into context.User.Claims in my ASP.NET Core 5 Web API. I'm using Azure and have registered an app in Azure AD in our tenant.
The code is in an Authorization handler.
When I read the JWT token (context.Request.Headers["Authorization"]) using JwtSecurityTokenHandler, I can get all claims, but my context.User.Claims is still empty.
The aud and iss values show up as:
[aud, https://graph.microsoft.com]
[iss, https://sts.windows.net/[tenant-id]/]
This is my Startup.ConfigureServices() method:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
option.Audience = audience;
option.Authority = authority;
option.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromHours(1),
ValidateLifetime = true,
ValidateIssuer = true,
ValidIssuer = authority,
ValidateAudience = true,
ValidAudience = audience
};
});
What am I supposed to put in audience and authority? I have tested the values I got from the JWT token (above).
I have also tried with:
authority (issuer): https://login.microsoftonline.com/[tenant-id]/v2.0
audience: app://[client-id]
and all combinations. Same result
Initially, try to decode the token using jwt.ms and check what claims the token contains.
For Audience parameter, you can use the Application ID URI (api://your_app_id) or scope (https://graph.microsoft.com).
For Authority parameter, you can use the address of the token-issuing authentication server. Please note that, issuer value differs depending on the type of token you are generating (v1.0/v2.0).
For v1.0 token -> https://sts.windows.net< Azure AD Tenant GUID>/
For v2.0 token -> https://login.microsoftonline.com<Azure AD Tenant GUID>/v2.0
To confirm and know more about the parameters, refer to the blog by Jeffrey Fritz.
To receive the claims, make sure to add [Authorize] attribute with HTTP
context header.
Please check whether you included app.UseAuthorization or not.
Make sure to call the middleware in the order like below from this MS Doc:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
If the issue persists, then try to modify the Startup.cs -> ConfigureServices() method as mentioned in this blog.
You can refer to the links below that can give you some pointers to resolve the issue:
identityserver4 - User.Claims is empty ASP.NET 5.0 - Stack Overflow
.NET Core Web API HttpContext.User.Claimsare always null - Stack Overflow
I suspect you are getting the wrong type of JWT, with a nonce field in the JWT header, that does not validate properly in your own APIs. To fix this there is an Expose an API scope option.
There are some visual details about this from step 3 of my blog post from a few years back. You can then configure the issuer and audience as in my API code example config.

Having Issues Validating a JWT token generated by a Node JS Express API from a C# .NET 6 API

Background Context
I have two local APIs one API is created using Express and Node.JS, This is called my Auth API and another API Created using C# .NET 6 this will be used for adding blog posts. I will call this API 2 for the purpose of this post.
API 2 has an endpoint called "Signin" this sends a request over to the Auth API and if the login is a success the Auth API sends back a response about the user and a JWT token this was signed and created using the npm package https://www.npmjs.com/package/jsonwebtoken.
the JWT returned is this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRhdGFiYXNlam9lIn0.eyJpZCI6IjYyMWEzOWFiNTUwMGE1NjA5NzI1MTIwMCIsImVtYWlsIjoiam9lQGRhdGFiYXNlam9lLmNvbSIsImlhdCI6MTY0NzMzODk0MSwiZXhwIjozMjk0NjgxNDgyLCJhdWQiOiJkYXRhYmFzZWpvZSIsImlzcyI6ImRhdGFiYXNlam9lIiwic3ViIjoiZGF0YWJhc2Vqb2UifQ.SVNpwte2R9lVjHqUlrM7syphcKGgSOsBxhduwHCDnq4
The Problem
the problem is that API 2 when trying to validate the token is complaining about "'IDX10516: Signature validation failed. Unable to match key:"
the code for this is:
public void validateJwt(HttpContext context, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("databasejoe");
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
}
catch (Exception ex)
{
// do nothing if jwt validation fails
//https://stackoverflow.com/questions/38725038/c-sharp-how-to-verify-signature-on-jwt-token
}
What I have Checked
I have confirmed that the Auth API and API 2 both have the correct signing key, to keep things simple and for local testing I am using the signing key "databasejoe".
researched google and refactored my code to no avail.
Outcome
The outcome that I am trying to achieve is to have API 2 validate the token that was generated by the Auth API with success.
Your help and advice would be appreciated.

How to support multiple tenants and secret tokens in azure ad scim provisioning

I am trying to create Azure AD provisioning for our Saas product (using scim2).
I want multiple customers to be able to connect with their Azure AD tenant.
Microsoft has reference code here: https://github.com/AzureAD/SCIMReferenceCode
However, that is setup to only allow one tenant and also to not use the "secret token" that you set up in azure ad. Even tho the comment specifically states the secret token should not be left empty for production.
Here is the important piece of code from the reference project
// Leave the optional Secret Token field blank
// Azure AD includes an OAuth bearer token issued from Azure AD with each request
// The following code validates the Azure AD-issued token
// NOTE: It's not recommended to leave this field blank and rely on a token generated by Azure AD.
// This option is primarily available for testing purposes.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = this.configuration["Token:TokenIssuer"];
options.Audience = this.configuration["Token:TokenAudience"];
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
return Task.CompletedTask;
},
OnAuthenticationFailed = AuthenticationFailed
};
});
With that code it works assuming Token:TokenIssuer setting is https://sts.windows.net/<tenant_id>/ where tenant_id is the actual tenant id and TokenAudience is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 (non gallery app).
But it only works if I leave the "Secret token" empty when I set it up in azure ad (non gallery app under Entrprise applications).
I have tried all sorts of things, adding OnChallenge tells me a challenge is sent if I set the "Secret token" but beyond that I am not getting much further.
Any sample code for handling multiple tenants and secret tokens here would be amazing
Update:
Using options.TokenValidationParameters.IssuerValidator I can validate the issuer and thus make that work with multiple tenants. What I really can't get past right now is making a call work when I enter a "Secret token" here: (see picture)
So I figured out that what they want is a JWT token in that field that I generate.
So first I created a method that generates a web token
private string GenerateJSONWebToken()
{
// Create token key
SymmetricSecurityKey securityKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Token:TokenSigningKey"]));
SigningCredentials credentials =
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
// Set token expiration
DateTime startTime = DateTime.UtcNow;
DateTime expiryTime = startTime.AddMinutes(120);
// Generate the token
JwtSecurityToken token =
new JwtSecurityToken(
configuration["Token:TokenIssuer"],
configuration["Token:TokenAudience"],
null,
notBefore: startTime,
expires: expiryTime,
signingCredentials: credentials);
string result = new JwtSecurityTokenHandler().WriteToken(token);
return result;
}
In my appsettings.json I added
{
"Logging": {
...
},
"Token": {
"TokenAudience": "xxx-xxx-xxx-xxx",
"TokenIssuer": "https://sts.windows.net/yyyy-yyyy-yyyy/",
"TokenSigningKey": "zzz"
}
}
Token Audience I set to 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 as can be read about here https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups.
TL;DR The audience for the token will be the application template ID for the application in the gallery, the application template ID for all custom apps is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621
yyyy part of TokenIssuer is the tenant id of the azure ad tenant
zzz from signing key is simply a key of your choosing.
Now finally I generated a token containing the values from appsettings.json.
I then pasted this key into the "Secret token" field in Azure AD.
Finally, how to make this multi tenant (my next steps)
Remove Token:TokenIssuer from appsettings.json
When you call GenerateJSONWebToken send in the client Azure AD tenant ID and use that instead of static value from appsettings.json (Either your client will give you this, or you have it from connecting your app to them)
In startup.cs notice I have already implemented IssuerValidator. Update this to validate not against appsettings.json but your data store.
You can visit Managing user account provisioning for enterprise apps in the Azure portal know more about the setup:

Getting Claims from OAuth Authorization Service in ASP.Net MVC 6

I am using the following code to successfully get a token from my MVC web app. However I am unsure how to retrieve the claims that I have added. Should they be returned in the same response as my token?
Thanks!
Startup.cs:
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.Audience = "resource_server";
options.Authority = "https://www.example.com/";
options.RequireHttpsMetadata = false;
});
app.UseOpenIdConnectServer(options =>
{
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = false;
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = "/connect/token";
});
Adding claims:
identity.AddClaim("custom_claim", "value", "token id_token");
foreach (string role in await userManager.GetRolesAsync(user))
{
identity.AddClaim(ClaimTypes.Role, role, "id_token token");
}
This is my PostAsync result:
{"resource":"resource_server","scope":"openid profile","token_type":"bearer","access_token":"eyJhbGciOiJSU....","expires_in":"3600"}
Should they be returned in the same response as my token?
Since you're specifying both the id_token and token destinations, your claims should be copied in both the access token and the identity token. You should be able to use any JSON parser to extract the claims you're looking for from the access_token/id_token properties.
Two remarks:
In ASOS beta4, you had to explicitly add scope=openid to your token request to get back an identity token, even if you called ticket.SetScopes("openid"), which probably explains why there's no id_token property in the response you shared. This policy was relaxed in the next version.
In ASOS beta4 (for ASP.NET Core RC1), access tokens were serialized using the JWT format. This is no longer true with the beta5 version, that uses an encrypted format by default. Don't try to read them from the client application: instead, use the id_token, which is meant to be consumed by the client app.

Categories

Resources