Validate jwt token from ASP.NET Core controller when not using Authorize attribute - asp.net-core

My client is sending the jwt token with some requests. Almost all requests is hitting a web api controller that is using the [Authorize] attribute. With this I am certain that my jwt token is properly validated, but for certain endpoints is really just want to grab the JWT token and get the sub value. I do this by using this extension method:
public static class HttpContextAccessorExtensions
{
public static string GetUserIdFromToken(this IHttpContextAccessor httpContextAccessor)
{
if (httpContextAccessor == null)
{
throw new ArgumentNullException(nameof(httpContextAccessor));
}
var context = httpContextAccessor.HttpContext;
string userId = null;
if (httpContextAccessor != null)
{
if (context != null)
{
var request = context.Request;
if (request != null)
{
request.Headers.TryGetValue("Authorization", out var bearer);
if (bearer.Any())
{
string t = bearer[0].Split(" ")[1];
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(t) as JwtSecurityToken;
var utcNow = DateTime.UtcNow;
if (utcNow >= token.ValidFrom &&
utcNow <= token.ValidTo)
{
userId = token.Claims.FirstOrDefault(_ => _.Type.Equals("sub")).Value;
}
else
{
userId = String.Empty;
}
}
}
}
}
return userId;
}
}
My only problem here is that the jwt token isn't validated, so I am guessing that a "bad person" could just mingle with the valid to datetime, and keep extending the token lifetime. Please correct me if I am wrong on this.
What i want to know is: is there a way for me to validate the token? I know the JwtSecurityTokenHandler can call "ValidateToken", but this method needs a signing key, and I really dont know how to get this. I use IdentityServer 4 to generate tokens. Is there some easy way of injecting the Key into the IoC so I can get it, or is there an easy way to validate the token, that I dont know of?
Any help is appreciated

Since your access token is generated by IdentityServer4, then you should validate it using the IS4 Introspection Endpoint. This will give you the definitive answer as to if it is valid and whether or not it is still active.
Information about the Introspection Endpoint is in the IS4 docs at:
http://docs.identityserver.io/en/latest/endpoints/introspection.html
As referenced in those docs, perhaps the easiest way to interact with the endpoint is to use the IdentityModel client library (add package via NuGet), which adds the IntrospectTokenAsync() extension method to HttpClient and returns a TokenIntrospectionResponse object that has the information you need.
IdentityModel client Introspection Endpoint docs:
https://identitymodel.readthedocs.io/en/latest/client/introspection.html

Inside your controller you can just do this:
if(HttpContext.User.Identity.IsAuthenticated)
{
var claims = HttpContext.User.Claims;
}
and you can inspect the claims for whatever information is of interest.
that authenticated user identity is already validated and is available regardless of if the Authorize attribute is applied. If the user's cookie has expired etc that will return false. .

What i want to know is: is there a way for me to validate the token? I know the JwtSecurityTokenHandler can call "ValidateToken", but this method needs a signing key, and I really dont know how to get this. I use IdentityServer 4 to generate tokens.
When validating JWT token issued by Identity Server(using key pairs to issue/validate token) , specific for validating signature . API/resource server will pull down (and might cache) your identity providers discovery document located at OIDC endpoint : https://xxx/.well-known/openid-configuration. This document contains materials that allow the resource server to validate the token ,read available keys from jwks_uri .
Using key pairs menans the JWT token which is signed by IDS4 with private key. A JWT token is a non-encrypted digitally signed JSON payload which contains different attributes (claims) to identify the user/role. The signature is the last part of the JWT and needs to be used for verification of the payload. This signature was generated with the algorithm described in the header(RS256 for example) to prevent unauthorized access. In your api , you could use public key which published by IDS4(jwks_uri) to validate the signature of the JWT token . Please refer to this document for more details about JWT token .
You can use JwtBearerAuthentication middleware or IdentityServer.AccessTokenValidation middleware will help do that process . Code sample here is for your reference . If you want to manually validating a JWT token . Click here for code sample .
My only problem here is that the jwt token isn't validated, so I am guessing that a "bad person" could just mingle with the valid to datetime, and keep extending the token lifetime.
It's not recommended to use token without validating it . Since the signature part of JWT token is encrypted/encode use (header+payload) , even someone change the claims(expire time) , it won't pass the validation since he can't know Identity Server's private key to issue a correct signature .

Related

JWT Token is expired or not?

How to check whether the current JWT Token is expired or not in .NET Core application.
string token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
I got the token value using above code. Similar i need a way to find the token is expired or not.
You can get the token expire time using this:
string accessTokenExpire = await HttpContext.GetTokenAsync("expires_at");
Alternatively, you as a client don't care about the expire time and instead renew the access token when the access token is rejected by the API receiving it.
Important, you are in the client, not supposed to look inside the access token.
We can find JWT token expired or not using below code.
private readonly IHttpContextAccessor _httpContextAccessor;
var expiresAt = DateTimeOffset.Parse(await _httpContextAccessor.HttpContext.GetTokenAsync("expires_at"));
if (expiresAt != null && expiresAt < DateTime.UtcNow)
{
//Token Expired
}

How can I change the 'typ' of a token provided by Azure AD?

I have a project setup like this:
React frontend
-> authenticates against...
Identity Server
-> which redirects to...
A Microsoft login
I'm using a Clients Credential Provider and it works great - the IS4 redirects to MS login, and then gets redirected with the access token back, which is then passed on to the React app.
Now, I've been tasked with creating a feature to change the user's password. I'm trying to do this by sending the old+new password to IS4, and then calling the MSGraphClient, but I couldn't make it work.
I've tried the Username/Password provider, because I have all the info needed, but I need to change stuff on the ActiveDirectory settings to make my app public. But even then, I don't like that solution.
I've also tried with the On-behalf-of provider, this is the code:
var scopes = new[] { "User.Read",
"Directory.AccessAsUser.All" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "~~";
// Value from app registration
var clientId = "~~";
var clientSecret = "~~";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// This is the incoming token to exchange using on-behalf-of flow
var oboToken = HttpContext.Request.Headers.First(h => h.Key == "Authorization").Value.ToString().Replace("Bearer ", "");
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new DelegateAuthenticationProvider(async (request) => {
// Use Microsoft.Identity.Client to retrieve token
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
And it kinds of work, because the request is made, but the server throws an error:
AADSTS5002727: Invalid JWT header type specified. Allowed types: 'JWT','http://openid.net/specs/jwt/1.0'.
I checked my token on JWT.io, and the typ is at+jwt... Why? Why is MS sending me a type of token that it doesn't support? How can I change it from my side so it's a plain JWT?
Thanks for any advice, and any other possible solution for this.
To resolve the error "AADSTS5002727: Invalid JWT header type specified. Allowed types: JWT,http ://openid.net/specs/jwt/1.0" , please try the below if helpful:
Please check the version of .Net core you are currently using to generate the token. Try using .Net core 2.2 with IS4.
Try setting IdentityServerOptions.AccessTokenJwtType to empty string or JWT on IdentityServerOptions.
In the mentioned code, replace var oboToken variable directly with the value of token.
var oboToken = "JWT_TOKEN_TO_EXCHANGE";
Please note the below point from MsDoc :
Don't attempt to validate or read tokens for any API you don't own,
including the tokens in this example, in your code. Tokens for Microsoft services can use a special format that will not validate as
a JWT, and may also be encrypted for consumer (Microsoft account)
users
If still the error persists, try upgrading clients to a new token validation library that works with the new style tokens.
Please check whether the below links give you any pointer to resolve the issue:
JWT Token always Invalid · Issue #905 · openiddict/openiddict-core · GitHub
IdentityServer .Net Core 3.0 & Owin/Katana Token validation · Issue #3705 · IdentityServer/IdentityServer4 · GitHub

Asp.Net Core jwt token is transformed after authentication

I have Cognito id token with email claim.
..........
"iat": 164456734,
"jti": "81ac2634-e241-444f-88cf-eabf454644",
"email": "david#mail.com"
}
However, after asp net core jwt middleware authentication email claim is transformed from email type to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress - ClaimTypes.Email in C#.
But then I read the token manually:
var token = new JwtSecurityTokenHandler().ReadJwtToken(jwtToken);
var claimsIdentity = new ClaimsIdentity(token.Claims);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity)
Claim type is not transformed and remains email.
Why in asp net core authentication claim is transformed to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress?
Can I create claimsPrincipal manually having this email claim transformation without manually modifying Claims list?
So, Microsoft and OpenIDConnect have different opinions for what the email claim name should be and to disable this remapping you can do either:
public void ConfigureServices(IServiceCollection services)
{
// By default, Microsoft has some legacy claim mapping that converts
// standard JWT claims into proprietary ones. This removes those mappings.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
// Or set this flag to false
.AddJwtBearer(opt =>
{
...
opt.MapInboundClaims = false;
});
It has in fact been understood as ClaimTypes.Email, however the string returned by this property is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress (source).
The token is not transformed, unless specifically done so, rather parsed and understood as ClaimTypes.Email with the actual token not modified.

Identity Server 4 : Custom OIDC login callback to store tokens in a store

I'm currently building a Blazor app that needs authentication and retrieve acces_token to access an API. I'm also using refresh_token to avoid user relogin when browsing.
My current way to manage it is to register a custom httpClient in dependency injection system, retrieve HttpContext to check for access_token and if not good, call manually idsrv4 to retrieve new access & refresh tokens
The main issue is that by doing so, I cannot write into httpContext the new values (response has already begin) so I'll have to do it everytime, losing advantages of token lifetime.
My goal is to store all user informations & token informations within a custom store. To do so, I'll have to handle the point when tokens are retrieved, but I can't find a god place to fit in as callback ?
I've already tried the Events property within the AddOpenIdConnect options, but it doesn't seems any of them can fit my needs ?
Where can I put my custom callback logic after login ?
If you want to get and store the access token after client app get the access token , you can use OnTokenResponseReceived event :
options.Events = new OpenIdConnectEvents
{
OnTokenResponseReceived = ctx =>
{
var accessToken = ctx.TokenEndpointResponse.AccessToken;
var idToken = ctx.TokenEndpointResponse.IdToken;
return Task.CompletedTask;
},
};

How to do token based auth using ServiceStack

How would I implement the following scenario using ServiceStack?
Initial request goes to http://localhost/auth having an Authorization header defined like this:
Authorization: Basic skdjflsdkfj=
The IAuthProvider implementation validates against a user store and returns a session token as a response body (JSON).
The client uses this token an resends it against the subsequent requests like http://localhost/json/reply/orders using the Authorization header like this:
Authorization: BasicToken <TokenFromPreviousSuccessfulLogin>
Using a AuthenticateAttribute I want to flag my Service to use Authentication.
How should I implement the validation of the token for the subsequent requests?
How should I implement the IAuthProvider to provide the token?
How would I register the Providers etc.? Using RequestFilters or using the AuthFeature?
ServiceStack's JWT AuthProvider and API Key AuthProvider both use token based Authentication.
Otherwise the BasicAuth Provider is a very simple class that simply extracts the UserName and Password from the BasicAuth Header and evaluates it:
public override object Authenticate(...)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
if (basicAuth == null)
throw HttpError.Unauthorized("Invalid BasicAuth credentials");
var userName = basicAuth.Value.Key;
var password = basicAuth.Value.Value;
return Authenticate(authService, session, userName, password, request.Continue);
}
If you want to provide enhanced behavior, I would simply inherit this class check for the Authorization: BasicToken header, if it exists use that otherwise call the base.Authenticate(...) method to perform the initial validation.