I'm trying to understand the code block below. Everything is fine until the third argument of the tokenHandler.ValidateToken(...) method which is out SecurityToken validatedToken.
I checked the docs but could not find out much about that last argument. How does it work?
And what happens to tokenHandler.ValidateToken(...)? we are not assigning its return value to anything it looks like it just stays there and idk.
Could you make these clear for me?
private void attachUserToContext(HttpContext context, IUserService userService, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
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);
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetById(userId);
}
catch
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
The method ValidateToken() takes the received token as a String, validates the token according to the TokenValidationParameters and creates an object of type SecurityToken, which is returned via the out parameter.
In the next line this object is casted to the type JwtSecurityToken
var jwtToken = (JwtSecurityToken)validatedToken;
and then parsed
var userId = int.Parse(...)
to get the userId and finally the HttpContext context
get's populated
context.Items["User"] = userService.GetById(userId);
with the user information. If validation is successful, you have the user information available via the httpContext.
Related
I'm using Azure B2C in an ASP.NET application with OpenIdConnectAuthentication. My sign-in policy has an absolute session length of 90 minutes.
After the session expires, I'd like the user to be automatically redirected to the login page, so they know they've been logged out. This doesn't seem to happen automatically. I was hoping this could be done automatically, perhaps using OWIN.
For now, as a workaround, I set a Refresh header in all HTTP responses that uses the remaining session life (calculated based on the iat claim and current time). But I was hoping this could just be handled automatically from the server. Is there any way to make the redirect occur automatically from the server-side once the session expires?
Below is my Startup class in Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
// ... other code ... //
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy) {
var options = new OpenIdConnectAuthenticationOptions {
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
UseTokenLifetime = true,
// These are standard OpenID Connect parameters, with values pulled from
// Web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = logoutUri,
Notifications =
new OpenIdConnectAuthenticationNotifications {
AuthenticationFailed = AuthenticationFailed,
RedirectToIdentityProvider =
(context) => {
var value = context.OwinContext.Request.Path.Value;
if (value != "/default.aspx") {
context.OwinContext.Response.Redirect("/");
context.HandleResponse();
}
return Task.FromResult(0);
}
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters =
new TokenValidationParameters {
NameClaimType = "name",
},
};
return options;
}
I need to port over an application from ASP .NET WebApi to ASP .NET Core. The existing application uses an AuthorizationFilterAttribute to check for a Bearer JWT in the header and validates it manually by fetching the crypto keys from the issuer and checking some roles. I will post the existing code below.
As AuthorizationFilter-Attributes are not a thing anymore in ASP .NET Core, I am looking for the correct way to do this now.
The samples and documentation I found are all about issuing your own tokens, which I do not want. My attempt to write my own policy to check the token failed because apparently there needs to be authentication done before you can use policies. The existing application does not do any authentication, it's simply a matter of fetching the JWT and validating it against the issuer as can be seen in the code below.
Any help on how to guard a controller action by checking a supplied (issued by another system) OpenID-Connect token would be greatly appreciated!
Existing code for validating the supplied JWT:
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtSecurityToken = jwtSecurityTokenHandler.ReadJwtToken(token);
string openIdConnectDiscoveryUrl = jwtSecurityToken.Issuer.Trim();
if (!openIdConnectDiscoveryUrl.EndsWith("/", StringComparison.Ordinal))
{
openIdConnectDiscoveryUrl += "/";
}
openIdConnectDiscoveryUrl += ".well-known/openid-configuration";
string n;
string e;
using (HttpClient httpClient = new HttpClient())
{
string openidConfiguration = httpClient.GetStringAsync(new Uri(openIdConnectDiscoveryUrl, UriKind.Absolute)).Result;
dynamic openidConfigurationJson = JObject.Parse(openidConfiguration);
string jwksUri = openidConfigurationJson.jwks_uri;
string certs = httpClient.GetStringAsync(new Uri(jwksUri, UriKind.Absolute)).Result;
dynamic certsJson = JObject.Parse(certs);
dynamic key = certsJson.keys[0];
n = key.n;
e = key.e;
}
using (RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider())
{
rsaCryptoServiceProvider.ImportParameters(new RSAParameters { Modulus = FromBase64Url(n), Exponent = FromBase64Url(e) });
TokenValidationParameters validationParameters = new TokenValidaAtionParameters
{
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidateActor = false,
ValidateTokenReplay = false,
RequireSignedTokens = true,
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = new RsaSecurityKey(rsaCryptoServiceProvider)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
handler.ValidateToken(token, validationParameters, out SecurityToken validatedSecurityToken);
jwtSecurityToken = validatedSecurityToken as JwtSecurityToken;
// removed some code that checks the existence of specific roles in the token
Iam working on app, which consists from angular frontend and ASP net Web API backend(.net 4.5). For authentication iam using OpenIdConnect. I succesfully connected frontend to identity provider but now i need to validate id token on backend, so i can be sure, that only validated users can call backend.
This id token use rs256 algorithm for signing. So on backend, i need to do two things:
Get JWKs from identity provider URL - iam a little lost here, should i get it throug normal HttpClient, or there is some library or helper function to do this?
Generate RSA public key out of JWKs and validate token - for this iam using this function:
string token="xyz..";
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(
new RSAParameters()
{
Modulus = FromBase64Url("xyz.."),
Exponent = FromBase64Url("xyz..")
});
var validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = true,
IssuerSigningKey = new RsaSecurityKey(rsa)
};
SecurityToken validatedSecurityToken = null;
var handler = new JwtSecurityTokenHandler();
handler.ValidateToken(tokenStr, validationParameters, out validatedSecurityToken);
JwtSecurityToken validatedJwt = validatedSecurityToken as JwtSecurityToken;
It works, but now i need to connect it somehow with the loaded JWKs and register it to use this for every request that comes. Any advices or simple example would really help me. Thx.
This code below is taken from one of my training classes and it will automatically download and validate the provided token and I hope you can use it as a reference. You typically will use the ConfigurationManager to download the IdentityServer configuration and JWKS for you. It will also internally cache and periodically (every 24) readload the config.
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using OpenID_Connect_client.Models;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
namespace OpenID_Connect_client
{
public class TokenValidator
{
private readonly IOpenIDSettings openIDSettings;
public TokenValidator(IOpenIDSettings openIDSettings)
{
this.openIDSettings = openIDSettings;
}
public string ValidateToken(string token, string clientId)
{
try
{
string issuer = openIDSettings.Issuer;
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{issuer}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result;
// Configure the TokenValidationParameters. Assign the SigningKeys which were downloaded from Auth0.
// Also set the Issuer and Audience(s) to validate
//https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
var validationParameters =
new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidAudiences = new[] { clientId },
ValidIssuer = issuer,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateTokenReplay = true
};
// Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(token, validationParameters, out validatedToken);
// The ValidateToken method above will return a ClaimsPrincipal. Get the user ID from the NameIdentifier claim
// (The sub claim from the JWT will be translated to the NameIdentifier claim)
return $"Token is validated. User Id {user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value}";
}
catch (Exception exc)
{
return "Invalid token: " + exc.Message;
}
}
}
}
I'm trying to convert my token string to jwt token using JwtSecurityTokenHandler. But it's getting error that saying
IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII is hidden]'.\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
How can I solve this issue?
Here is my token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwibmJmIjoxNTUwNjM3NzcxLCJleHAiOjE1NTA2Mzg5NzEsImlhdCI6MTU1MDYzNzc3MX0.tUcoyoHgkrX3rDKl0cRLd9FwLtRprQpgYepMoiekixY
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
Calling web api
using (HttpClient client = new HttpClient())
{
string path = "UserMaintenance/ValidateUserId?userid=" + txtUsername.Text.Trim().ToString();
client.BaseAddress = new Uri(GlobalData.BaseUri);
client.DefaultRequestHeaders.Add("Authorization", "Bearer" + GlobalData.Token);
HttpResponseMessage response = client.GetAsync(path).Result;
if (response.IsSuccessStatusCode)
{
var value = response.Content.ReadAsStringAsync().Result;
isValid = JsonConvert.DeserializeObject<bool>(value);
}
}
Here is my GetPrincipal method
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken securityToken;
var principal = handler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception ex)
{
return null;
}
}
This is how I do it and it works for me:
var token = new System.IdentityModel.Tokens.JwtSecurityToken(jwt);
The above line works for System.IdentityModel.Tokens.Jwt package version 4.0.0.
As #Nick commented, in the latest versions of the package, the JwtSecurityToken does not exist in the previous namespace anymore, instead it exists in System.IdentityModel.Tokens.Jwt so you need to write:
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(jwt);
Unless your token is not well-formed. It would be better if you share the token too.
Update:
You also need to remove the word "Bearer " from the beginning of the token (If you haven't):
var jwt = context.Request.Headers["Authorization"].Replace("Bearer ", string.Empty);
at version 5.6.0.0 - currently is the latest version
can use similar code as in #thilim9's question.
var tokenId = identity.Claims.SingleOrDefault(c => c.Type == "id_token")?.Value;
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.ReadJwtToken(tokenId);
For .net framework 4.5.1 I remove my custom key while generating token and use default values in claims of JwtRegisteredClaimNames.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.GivenName, Data.UserName),
new Claim(JwtRegisteredClaimNames.Prn,Data.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
I have tried implementing ASOS with .net core 2.1 and there were few things which were available in OAuthAuthorizationProvider but I couldn't find them in ASOS. Also I think the context is little different in ASOS, So is there any alternate of the following code in ASOS:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
var options = new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = new PathString(AuthorizePath),
TokenEndpointPath = new PathString(TokenPath),
ApplicationCanDisplayErrors = true,
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
#if DEBUG
AllowInsecureHttp = true,
#endif
// Authorization server provider which controls the lifecycle of Authorization Server
Provider = new OAuthAuthorizationServerProvider
{
OnValidateClientRedirectUri = ValidateClientRedirectUri,
OnValidateClientAuthentication = ValidateClientAuthentication,
OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials,
OnGrantClientCredentials = GrantClientCredetails
},
// Authorization code provider which creates and receives authorization code
AuthorizationCodeProvider = new AuthenticationTokenProvider
{
OnCreate = CreateAuthenticationCode,
OnReceive = ReceiveAuthenticationCode,
},
// Refresh token provider which creates and receives referesh token
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
,
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
Update:
private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("claim", x)));
context.Validated(identity);
return Task.FromResult(0);
}
Most of the options are still there but the events model has been reworked:
OnValidateClientRedirectUri was replaced by a more general OnValidateAuthorizationRequest event.
OnValidateClientAuthentication no longer exists. Client authentication validation is now performed in the OnValidateTokenRequest event (or OnValidateIntrospectionRequest/OnValidateRevocationRequest, but you're not using the introspection/revocation endpoints in your snippet).
The *Provider properties - used for decrypting/encrypting tokens - have been replaced by Serialize* and Deserialize* events. Using them is no longer mandatory: in this case, authorization codes and refresh tokens will be considered valid until they expire.
If you want to learn more about the revamped events model, don't miss this blog post series: https://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-introduction/