I am attempting to create a token validation method that returns true if a JWT token is valid based on the signature. I don't think I really need to validate everything in the token but what actually signifies a token is valid after calling ValidateToken()? The existence of a principle? The out referenced token contains certain values? Not sure when to return true from this method.
public bool ValidateToken(string tokenString)
{
var validationParameters = new TokenValidationParameters()
{
ValidIssuer = "My Company",
ValidAudience = ApplicationId,
IssuerSigningKey = JsonWebTokenSecretKey
};
SecurityToken token = new JwtSecurityToken();
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(tokenString, validationParameters, out token);
return principal != null;
}
I check all of the claims values manually. I've been searching for a definitive answer to your same question but the only thing I have seen is that the ValidateToken function will throw an Exception if something is wrong, so I begin by wrapping the call in a try-catch and return false from the catch.
That's just my "first-pass" at validating the token, though. Afterwards I do a little more heavy lifting to check certain values manually. For example, I make sure that the unique_name value in the claims section actually exists as a user in my database, that the user has not been deactivated, and other proprietary system stuff like that.
public static bool VerifyToken(string token)
{
var validationParameters = new TokenValidationParameters()
{
IssuerSigningToken = new BinarySecretSecurityToken(_key),
ValidAudience = _audience,
ValidIssuer = _issuer,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken = null;
try
{
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
}
catch(SecurityTokenException)
{
return false;
}
catch(Exception e)
{
log(e.ToString()); //something else happened
throw;
}
//... manual validations return false if anything untoward is discovered
return validatedToken != null;
}
The last line, return validatedToken != null, is purely superstition on my part. I've never seen the validatedToken be null.
Related
I am using AspNet Core to build a web api and JWT tokens to authenticate users.
I see that in TokenValidationParameters the default value of ValidateIssuerSigningKey property is false.
Does it make any difference if we set it to true, when using the HMAC256 Symmetric key to sign and verify tokens (where there is no public-key added to the token like in case of RSA)?
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
string jwtIssuer = configuration["JwtIssuer"];
SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtKey"]));
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtIssuer,
ValidAudience = jwtIssuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey,
ClockSkew = TimeSpan.Zero
};
});
Or is it necessary to set ValidateIssuerSigningKey to true only when using RSA keys?
Here is the code level documentation of this property:
//
// Summary:
// Gets or sets a boolean that controls if validation of the Microsoft.IdentityModel.Tokens.SecurityKey
// that signed the securityToken is called.
//
// Remarks:
// It is possible for tokens to contain the public key needed to check the signature.
// For example, X509Data can be hydrated into an X509Certificate, which can be used
// to validate the signature. In these cases it is important to validate the SigningKey
// that was used to validate the signature.
[DefaultValue(false)]
public bool ValidateIssuerSigningKey { get; set; }
Based on looking at the Microsoft.IdentityModel.Tokens source code, I could find only one place where the ValidateIssuerSigningKey boolean property is used, here:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Tokens/Validators.cs
Which ultimately causes this code block to be executed:
X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey;
if (x509SecurityKey?.Certificate is X509Certificate2 cert)
{
DateTime utcNow = DateTime.UtcNow;
var notBeforeUtc = cert.NotBefore.ToUniversalTime();
var notAfterUtc = cert.NotAfter.ToUniversalTime();
if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10248, notBeforeUtc, utcNow)));
LogHelper.LogInformation(LogMessages.IDX10250, notBeforeUtc, utcNow);
if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate()))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10249, notAfterUtc, utcNow)));
LogHelper.LogInformation(LogMessages.IDX10251, notAfterUtc, utcNow);
}
I.e. that flag relates to X509 certificates only, and the testing of the time period they are valid for. So I suspect it does not affect tokens validated using HMAC256... unless the HMAC key was obtained from an X509 certificate!
The Azure Active Directory people clarify this on this GitHub Wiki page created in December 2020:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/Use-of-TokenValidationParameters.ValidateIssuerSigningKey
I'll quote key parts for convenience:
Normally this [ValidateIssuerSigningKey] is not required because the user / runtime must set IssuerSigningKey or IssuerSigningKeys or in the case of custom security key retrieval the delegate IssuerSigningKeyResolver ( Definition ) for keys to be available for validating the signature on the token. ... It is assumed that only keys from trusted sources are set.
If you need custom validation of the security key that signed the token you can ... set TokenValidationParameters.ValidateIssuerSigningKey to true. ...
The default behavior is applicable to X509SecurityKey and checks that the certificate is not expired. No CRL or other checks are made.
I've been making a web api in F#, mostly following this guide: https://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/. However, I've been getting this error whenever I try to hit an authenticated endpoint in my aspnet webapi:
Failed to validate the token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiciIsImV4cCI6IjE1MjI2MzUwNDMiLCJuYmYiOiIxNTIyNTQ4NjQzIn0.VofLygSMitkmEsTBFNG-7-3jMAZYkyvfwc2UIs7AIyw.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__6.MoveNext()
I've found similar issues linked here, but none whose resolution helped me. My Startup.fs looks like:
type Startup private () =
new (configuration: IConfiguration) as this =
Startup() then
this.Configuration <- configuration
// This method gets called by the runtime. Use this method to add services to the container.
member this.ConfigureServices(services: IServiceCollection) =
// Add framework services
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(fun options ->
options.TokenValidationParameters = TokenValidationParameters (
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
ValidateLifetime = false, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5.0) //5 minute tolerance for the expiration date
) |> ignore
) |> ignore
services.AddMvc() |> ignore
services.AddSwaggerGen (fun c -> c.SwaggerDoc("v1", Swagger.Info())) |> ignore
services.AddCors() |> ignore
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
member this.Configure(app: IApplicationBuilder, env: IHostingEnvironment) =
app.UseExceptionHandler(
fun options ->
options.Run(
fun context ->
let ex = context.Features.Get<IExceptionHandlerFeature>()
match ex.Error with
| HttpCodedException (code, message) ->
printfn "code: %i, msg: %s" (int code) message
context.Response.StatusCode <- int code
context.Response.WriteAsync(message)
| exn -> raise (exn)
)
) |> ignore
// let cors = Action<CorsPolicyBuilder> (fun builder -> builder.WithOrigins("http://localhost:3000").AllowAnyHeader().AllowAnyMethod() |> ignore)
app.UseCors(fun policy ->
policy.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials()
.AllowAnyMethod()
.Build() |> ignore
) |> ignore
app.UseAuthentication() |> ignore
app.UseMvc() |> ignore
member val Configuration : IConfiguration = null with get, set
I've tried turning off basically all of the validation, so I'm confused why this is still failing. If it's helpful, the place where I generate the tokens looks like:
let GenerateToken (username) =
let claims = [|
Claim (ClaimTypes.Name, username)
Claim (JwtRegisteredClaimNames.Exp, DateTimeOffset(DateTime.Now.AddDays(1.0)).ToUnixTimeSeconds().ToString())
Claim (JwtRegisteredClaimNames.Nbf, DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString())
|]
let cred =
new SigningCredentials(
SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
SecurityAlgorithms.HmacSha256
)
let token = JwtSecurityToken(JwtHeader(cred), JwtPayload(claims))
JwtSecurityTokenHandler().WriteToken(token)
Hoping someone can see what I'm doing wrong.
Finally figured this out. F# doesn't use = for assignment, it uses <-. So needed to change my service AddAuthenticaton call to:
services.AddAuthentication(fun options ->
options.DefaultScheme <- JwtBearerDefaults.AuthenticationScheme
options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme
).AddJwtBearer(fun options ->
options.TokenValidationParameters <- TokenValidationParameters (
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = false,
IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
ValidateLifetime = false, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5.0), //5 minute tolerance for the expiration date
ValidateActor = false,
ValidateTokenReplay = false
)
) |> ignore
Now everything works fine.
This has been working well for me.
JWT Authentication settings
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("thisKeyIs32CharactersLong1234567"))
ValidateIssuer = true,
ValidIssuer = "MyIssuer",
ValidateAudience = true,
ValidAudience = "MyAudience",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
And then to create the actual token
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.CreateToken(
new SecurityTokenDescriptor
{
Issuer = "MyIssuer",
Audience = "MyAudience",
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes("thisKeyIs32CharactersLong1234567")), SecurityAlgorithms.HmacSha512Signature),
Subject = new ClaimsIdentity(
new[] {
new Claim(ClaimTypes.Name, "My Name"),
new Claim(ClaimTypes.Sid, "My UID"),
new Claim(ClaimTypes.GroupSid, "My GID")
},
Expires = DateTime.Now + TimeSpan.FromMinutes("30")
});
// Save token
handler.WriteToken(securityToken);
Hope it helps.
I am trying to use refresh token when the access token expires. A similar so question is answered here. And a sample code to renew token by an action
And i end up with the following code in the startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var logger = loggerFactory.CreateLogger(this.GetType());
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
var httpContextAuthentication = x.HttpContext.Authentication;//Donot use the HttpContext.Authentication to retrieve anything, this cause recursive call to this event
var oldAccessToken = await httpContextAuthentication.GetTokenAsync("access_token");
var oldRefreshToken = await httpContextAuthentication.GetTokenAsync("refresh_token");
logger.LogInformation($"Refresh token :{oldRefreshToken}, old access token:{oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
logger.LogInformation("Refresh token requested. " + tokenResult.ErrorDescription);
if (!tokenResult.IsError)
{
var oldIdToken = await httpContextAuthentication.GetTokenAsync("id_token");
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await httpContextAuthentication.GetAuthenticateInfoAsync("Cookies");
info.Properties.StoreTokens(tokens);
await httpContextAuthentication.SignInAsync("Cookies", info.Principal, info.Properties);
}
x.ShouldRenew = true;
}
else
{
logger.LogInformation("Not expired");
}
}
}
});
The client setup is as follows
AllowAccessTokensViaBrowser = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = 86400,
AccessTokenLifetime = 10,
AllowOfflineAccess = true,
AccessTokenType = AccessTokenType.Reference
After successfully login, i am getting a 401 for every other request. And the log says
[Identity Server]2017-07-04 10:15:58.819 +01:00 [Debug]
"TjpIkvHQi../cfivu6Nql5ADJJlZRuoJV1QI=" found in database: True
[Identity Server]2017-07-04 10:15:58.820 +01:00 [Debug]
"reference_token" grant with value:
"..9e64c1235c6675fcef617914911846fecd72f7b372" found in store, but has
expired.
[Identity Server]2017-07-04 10:15:58.821 +01:00 [Error] Invalid
reference token. "{ \"ValidateLifetime\": true,
\"AccessTokenType\": \"Reference\", \"TokenHandle\":
\"..9e64c1235c6675fcef617914911846fecd72f7b372\" }"
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Token is
invalid.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Creating
introspection response for inactive token.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Information] Success
token introspection. Token status: "inactive", for API name: "api1"
Any help would by highly appreciated
UPDATE:
Basically, when the token expires i get a System.StackOverflowException on the following line
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
UPDATE 2:
Do not use HttpContext.Authentication to retrieve anything. Check my answer below to find the working implementaion
I was working on this for last two days and could not make this work. Funnily, after posting the question here, within 2 hours I make it working :)
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
WriteMessage($"{timeRemaining} and elapsed at {timeElapsed}");
if (timeElapsed > timeRemaining)
{
var oldAccessToken = x.Properties.Items[".Token.access_token"];
var oldRefreshToken = x.Properties.Items[".Token.refresh_token"];
WriteMessage($"Refresh token :{oldRefreshToken}, old access token {oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
if (!tokenResult.IsError)
{
var oldIdToken = x.Properties.Items[".Token.id_token"];//tokenResult.IdentityToken
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
x.Properties.StoreTokens(tokens);
WriteMessage($"oldAccessToken: {oldAccessToken}{Environment.NewLine} and new access token {newAccessToken}");
}
x.ShouldRenew = true;
}
}
}
Basically httpContextAuthentication.GetTokenAsync make this recursive, for that reason StackOverflowException occured.
Please let me know if this implementation has any issue
I am trying to implement a signature verification endpoint - or ASP.net WebAPI action filter, to verify that a token has in fact come from AWS Cognito - validate its signature.
I am using the following code, but it always returns invalid. The Javascript code example also below works perfectly with the same keys / token.
Can anyone help?
Thanks,
KH
CSharp
public IHttpActionResult Verify([FromBody] string accessToken)
{
string[] parts = accessToken.Split('.');
//From the Cognito JWK set
//{"alg":"RS256","e":"myE","kid":"myKid","kty":"RSA","n":"myN","use":"sig"}]}
var n = Base64UrlDecode("q7ocE2u-JSe1P4AF6_Nasae7e7wUoUxJq058CueDFs9R5fvWQTtAN1rMxBCeLQ7Q8Q0u-vqxr83b6N9ZR5zWUU2stgYzrDTANbIn9zMGDZvSR1tMpun5eAArKW5fcxGFj6klQ0bctlUATSGU5y6xmYoe_U9ycLlPxh5mDluR7V6GbunE1IXJHqcyy-s7dxYdGynTbsLemwmyjDaInGGsM3gMdPAJc29PXozm87ZKY52U7XQN0TMB9Ipwsix443zbE_8WX2mvKjU5yvucFdc4WZdoXN9SGs3HGAeL6Asjc0S6DCruuNiKYj4-MkKh_hlTkH7Rj2CeoV7H3GNS0IOqnQ");
var e = Base64UrlDecode("AQAB");
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportParameters(new RSAParameters
{
Exponent = new BigInteger(e).ToByteArrayUnsigned(),
Modulus = new BigInteger(n).ToByteArrayUnsigned()
});
SHA512Managed sha512 = new SHA512Managed();
byte[] hash = sha512.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
rsaDeformatter.SetHashAlgorithm(sha512.GetType().FullName);
if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
throw new ApplicationException(string.Format("Invalid signature"));
return Ok(true);
}
// from JWT spec
private static byte[] Base64UrlDecode(string input)
{
var output = input;
output = output.Replace('-', '+'); // 62nd char of encoding
output = output.Replace('_', '/'); // 63rd char of encoding
switch (output.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 1: output += "==="; break; // Three pad chars
case 2: output += "=="; break; // Two pad chars
case 3: output += "="; break; // One pad char
default: throw new System.Exception("Illegal base64url string!");
}
var converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
JavaScript
var jwkToPem = require('jwk-to-pem');
var jwt = require('jsonwebtoken');
var jwks = //jwk set file, which you can find at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
//Decode token
var decoded = jwt.decode(token, {complete: true});
//Get the correct key from the jwks based on the kid
var jwk = jwks.keys.filter(function(v) {
return v.kid === decoded.header.kid;
})[0];
//Convert the key to pem
var pem = jwkToPem(jwk);
//Verify the token with the pem
jwt.verify(token, pem, function(err, decoded) {
//if decoded exists, its valid
});
Replace
SHA512Managed sha512 = new SHA512Managed();
by
SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
Don't forget to set properly the hash algorithm properly as well
rsaDeformatter.SetHashAlgorithm("SHA256");
Flo's answer works but its built into .net now. Using https://rafpe.ninja/2017/07/30/net-core-jwt-authentication-using-aws-cognito-user-pool/ which has more details on how to build it into the .net core middleware:
public RsaSecurityKey SigningKey(string Key, string Expo)
{
return new RsaSecurityKey(new RSAParameters()
{
Modulus = Base64UrlEncoder.DecodeBytes(Key),
Exponent = Base64UrlEncoder.DecodeBytes(Expo)
});
}
public TokenValidationParameters TokenValidationParameters()
{
// Basic settings - signing key to validate with, audience and issuer.
return new TokenValidationParameters
{
// Basic settings - signing key to validate with, IssuerSigningKey and issuer.
IssuerSigningKey = this.SigningKey(CognitoConstants.key,CognitoConstants.expo),
ValidIssuer = CognitoConstants.Issuer,
ValidAudience = CognitoConstants.clientid,//Same value you send in the cognito request url
// when receiving a token, check that the signing key
ValidateIssuerSigningKey = true,
// When receiving a token, check that we've signed it.
ValidateIssuer = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// Do not validate Audience on the "access" token since Cognito does not supply it but it is on the "id"
ValidateAudience = true,
// This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens locally and validating them on the same
// machines which should have synchronised time, this can be set to zero. Where external tokens are
// used, some leeway here could be useful.
ClockSkew = TimeSpan.FromMinutes(0)
};
}
private bool ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
if (tokenHandler.CanReadToken(token))
{
var validationParams = TokenValidationParameters();
SecurityToken validatedToken;
//ValidateToken throws if it fails so if you want to return false this needs changing
var principal = tokenHandler.ValidateToken(token, validationParams, out validatedToken);
return validatedToken != null;
}
return false;
}
We need to update users claims after they log in to our website. This is caused by changes in the users licenses done by another part of our system.
However I am not able to comprehend how to update the claims without logout/login.
Rigth now this is our client setup
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
//user validation host
Authority = UrlConstants.BaseAddress,
//Client that the user is validating against
ClientId = guid,//if not convertet to Gui the compare from the server fails
RedirectUri = UrlConstants.RedirectUrl,
PostLogoutRedirectUri = UrlConstants.RedirectUrl,
ResponseType = "code id_token token",
Scope = "openid profile email roles licens umbraco_api umbracoaccess",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
_logger.Info("ConfigureAuth", "Token valdidated");
var id = n.AuthenticationTicket.Identity;
var nid = new ClaimsIdentity(
id.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var uri = new Uri(n.Options.Authority + "/connect/userinfo");
var userInfoClient = new UserInfoClient(uri,n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
_logger.Info("ConfigureAuth", "AuthenticationTicket created");
},
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
_logger.Debug("ConfigureAuth", "id_token for logout set on request");
_logger.Debug("ConfigureAuth", "Old PostLogoutRedirectUri: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString());
n.ProtocolMessage.IdTokenHint = idTokenHint;
var urlReferrer = HttpContext.Current.Request.UrlReferrer.ToString();
if (!urlReferrer.Contains("localhost"))
{
n.ProtocolMessage.PostLogoutRedirectUri = GetRedirectUrl();
}
else
{
n.ProtocolMessage.PostLogoutRedirectUri = urlReferrer;
}
_logger.Debug("ConfigureAuth", string.Format("Setting PostLogoutRedirectUri to: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString()));
}
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
n.ProtocolMessage.RedirectUri = GetRedirectUrl2();
n.ProtocolMessage.AcrValues = GetCurrentUmbracoId();
_logger.Debug("ConfigureAuth", string.Format("Setting RedirectUri to: {0}", n.ProtocolMessage.RedirectUri.ToString()));
}
},
}
});
We get our custom claims in SecurityTokenValidated
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
I do not follow how to get this without doing a login? Any help is highly appreciated.
That's a reminder that you should not put claims into tokens that might change during the lifetime of the session.
That said - you can set a new cookie at any point in time.
Reach into the OWIN authentication manager and call the SignIn method. Pass the claims identity that you want to serialize into the cookie.
e.g.
Request.GetOwinContext().Authentication.SignIn(newIdentity);