/// <summary> /// Validates that an OpenIdConnect Response from "token_endpoint" is valid as per http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">the <see cref="OpenIdConnectProtocolValidationContext"/> that contains expected values.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolException">If the response is not spec compliant.</exception> /// <remarks>It is assumed that the IdToken had ('aud', 'iss', 'signature', 'lifetime') validated.</remarks> public virtual void ValidateTokenResponse(OpenIdConnectProtocolValidationContext validationContext) { if (validationContext == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext)); } // no 'response' is recieved if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21333)); } // both 'id_token' and 'access_token' are required if (string.IsNullOrEmpty(validationContext.ProtocolMessage.IdToken) || string.IsNullOrEmpty(validationContext.ProtocolMessage.AccessToken)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21336)); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21332)); } ValidateIdToken(validationContext); ValidateNonce(validationContext); // only if 'at_hash' claim exist. 'at_hash' is not required in token response. object atHashClaim; if (validationContext.ValidatedIdToken.Payload.TryGetValue(JwtRegisteredClaimNames.AtHash, out atHashClaim)) { ValidateAtHash(validationContext); } }
/// <summary> /// Validates that an OpenIdConnect Response from "useinfo_endpoint" is valid as per http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">the <see cref="OpenIdConnectProtocolValidationContext"/> that contains expected values.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolException">If the response is not spec compliant.</exception> public virtual void ValidateUserInfoResponse(OpenIdConnectProtocolValidationContext validationContext) { if (validationContext == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext)); } if (string.IsNullOrEmpty(validationContext.UserInfoEndpointResponse)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21337)); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21332)); } string sub = string.Empty; try { // if user info response is a jwt token var handler = new JwtSecurityTokenHandler(); if (handler.CanReadToken(validationContext.UserInfoEndpointResponse)) { var token = handler.ReadToken(validationContext.UserInfoEndpointResponse) as JwtSecurityToken; sub = token.Payload.Sub; } else { // if the response is not a jwt, it should be json var payload = JwtPayload.Deserialize(validationContext.UserInfoEndpointResponse); sub = payload.Sub; } } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21343, validationContext.UserInfoEndpointResponse), ex)); } if (string.IsNullOrEmpty(sub)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21345)); } if (string.IsNullOrEmpty(validationContext.ValidatedIdToken.Payload.Sub)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21346)); } if (!string.Equals(validationContext.ValidatedIdToken.Payload.Sub, sub, StringComparison.Ordinal)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21338, validationContext.ValidatedIdToken.Payload.Sub, sub))); } }
/// <summary> /// Validates the 'token' according to http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the protocol message to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the validationContext contains a 'token' and there is no 'at_hash' claim in the id_token.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the validationContext contains a 'token' and the 'at_hash' claim is not a string in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the 'at_hash' claim in the 'id_token' does not correspond to the 'access_token' in the <see cref="OpenIdConnectMessage"/> response.</exception> protected virtual void ValidateAtHash(OpenIdConnectProtocolValidationContext validationContext) { if (LogHelper.Logger.IsTraceLevelEnabled()) { LogHelper.Logger.LogTrace(LogMessages.IDX10309); } if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException("validationContext.ValidatedIdToken"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX10333)); } if (string.IsNullOrEmpty(validationContext.ProtocolMessage.AccessToken)) { if (LogHelper.Logger.IsInformationLevelEnabled()) { LogHelper.Logger.LogInformation(LogMessages.IDX10310); } return; } object atHashClaim; if (!validationContext.ValidatedIdToken.Payload.TryGetValue(JwtRegisteredClaimNames.AtHash, out atHashClaim)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidAtHashException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10312, validationContext.ValidatedIdToken))); } var atHash = atHashClaim as string; if (atHash == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidAtHashException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10311, validationContext.ValidatedIdToken))); } try { ValidateHash(atHash, validationContext.ProtocolMessage.AccessToken, validationContext.ValidatedIdToken.Header.Alg); } catch (OpenIdConnectProtocolException ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidAtHashException(LogMessages.IDX10348, ex)); } }
/// <summary> /// Validates the 'code' according to http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the protocol message to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the validationContext contains a 'code' and there is no 'c_hash' claim in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the validationContext contains a 'code' and the 'c_hash' claim is not a string in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the 'c_hash' claim in the 'id_token' does not correspond to the 'code' in the <see cref="OpenIdConnectMessage"/> response.</exception> protected virtual void ValidateCHash(OpenIdConnectProtocolValidationContext validationContext) { LogHelper.LogVerbose(LogMessages.IDX21304); if (validationContext == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext)); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext.ValidatedIdToken)); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21333)); } if (string.IsNullOrEmpty(validationContext.ProtocolMessage.Code)) { LogHelper.LogInformation(LogMessages.IDX21305); return; } object cHashClaim; if (!validationContext.ValidatedIdToken.Payload.TryGetValue(JwtRegisteredClaimNames.CHash, out cHashClaim)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(LogHelper.FormatInvariant(LogMessages.IDX21307, validationContext.ValidatedIdToken))); } var chash = cHashClaim as string; if (chash == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(LogHelper.FormatInvariant(LogMessages.IDX21306, validationContext.ValidatedIdToken))); } try { ValidateHash(chash, validationContext.ProtocolMessage.Code, validationContext.ValidatedIdToken.Header.Alg); } catch (OpenIdConnectProtocolException ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(LogMessages.IDX21347, ex)); } }
/// <summary> /// Validates the 'code' according to http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the protocol message to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the validationContext contains a 'code' and there is no 'c_hash' claim in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the validationContext contains a 'code' and the 'c_hash' claim is not a string in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">If the 'c_hash' claim in the 'id_token' does not correspond to the 'code' in the <see cref="OpenIdConnectMessage"/> response.</exception> protected virtual void ValidateCHash(OpenIdConnectProtocolValidationContext validationContext) { IdentityModelEventSource.Logger.WriteVerbose(LogMessages.IDX10304); if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException("validationContext.ValidatedIdToken"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX10333)); } if (string.IsNullOrEmpty(validationContext.ProtocolMessage.Code)) { IdentityModelEventSource.Logger.WriteInformation(LogMessages.IDX10305); return; } object cHashClaim; if (!validationContext.ValidatedIdToken.Payload.TryGetValue(JwtRegisteredClaimNames.CHash, out cHashClaim)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10307, validationContext.ValidatedIdToken))); } var chash = cHashClaim as string; if (chash == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10306, validationContext.ValidatedIdToken))); } try { ValidateHash(chash, validationContext.ProtocolMessage.Code, validationContext.ValidatedIdToken.Header.Alg); } catch (OpenIdConnectProtocolException ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidCHashException(LogMessages.IDX10347, ex)); } }
/// <summary> /// Validates the 'token' according to http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the protocol message to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the validationContext contains a 'token' and there is no 'at_hash' claim in the id_token.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the validationContext contains a 'token' and the 'at_hash' claim is not a string in the 'id_token'.</exception> /// <exception cref="OpenIdConnectProtocolInvalidAtHashException">If the 'at_hash' claim in the 'id_token' does not correspond to the 'access_token' in the <see cref="OpenIdConnectMessage"/> response.</exception> protected virtual void ValidateAtHash(OpenIdConnectProtocolValidationContext validationContext) { IdentityModelEventSource.Logger.WriteVerbose(LogMessages.IDX10309); if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException("validationContext.ValidatedIdToken"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogException <OpenIdConnectProtocolException>(LogMessages.IDX10333); } if (string.IsNullOrEmpty(validationContext.ProtocolMessage.AccessToken)) { IdentityModelEventSource.Logger.WriteInformation(LogMessages.IDX10310); return; } object atHashClaim; if (!validationContext.ValidatedIdToken.Payload.TryGetValue(JwtRegisteredClaimNames.AtHash, out atHashClaim)) { throw LogHelper.LogException <OpenIdConnectProtocolInvalidAtHashException>(LogMessages.IDX10312, validationContext.ValidatedIdToken); } var atHash = atHashClaim as string; if (atHash == null) { throw LogHelper.LogException <OpenIdConnectProtocolInvalidAtHashException>(LogMessages.IDX10311, validationContext.ValidatedIdToken); } try { ValidateHash(atHash, validationContext.ProtocolMessage.AccessToken, validationContext.ValidatedIdToken.Header.Alg); } catch (OpenIdConnectProtocolException ex) { throw LogHelper.LogException <OpenIdConnectProtocolInvalidAtHashException>(ex, LogMessages.IDX10348); } }
/// <summary> /// Validates that the 'state' in message is valid. /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'state' to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ProtocolMessage ' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'validationContext.State' is present in <see cref="OpenIdConnectProtocolValidationContext.State"/> but either <see cref="OpenIdConnectProtocolValidationContext.ProtocolMessage"/> or its state property is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'state' in the context does not match the state in the message.</exception> protected virtual void ValidateState(OpenIdConnectProtocolValidationContext validationContext) { if (!RequireStateValidation) { if (LogHelper.Logger.IsTraceLevelEnabled()) { LogHelper.Logger.LogTrace(LogMessages.IDX10342); } return; } if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX10333)); } // if state is missing, but not required just return. Otherwise process it. if (!RequireState && string.IsNullOrEmpty(validationContext.State) && string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { if (LogHelper.Logger.IsInformationLevelEnabled()) { LogHelper.Logger.LogInformation(LogMessages.IDX10341); } return; } else if (string.IsNullOrEmpty(validationContext.State)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10329, RequireState))); } else if (string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { // 'state' was sent, but message does not contain 'state' throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10330, RequireState.ToString()))); } if (!string.Equals(validationContext.State, validationContext.ProtocolMessage.State, StringComparison.Ordinal)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10331, validationContext.State, validationContext.ProtocolMessage.State))); } }
/// <summary> /// Validates that an OpenIdConnect Response from 'authorization_endpoint" is valid as per http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="validationContext">the <see cref="OpenIdConnectProtocolValidationContext"/> that contains expected values.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolException">If the response is not spec compliant.</exception> /// <remarks>It is assumed that the IdToken had ('aud', 'iss', 'signature', 'lifetime') validated.</remarks> public virtual void ValidateAuthenticationResponse(OpenIdConnectProtocolValidationContext validationContext) { if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } // no 'response' is received or 'id_token' in the response is null if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21333)); } if (string.IsNullOrEmpty(validationContext.ProtocolMessage.IdToken)) { // if 'code' is also not present, then throw. if (string.IsNullOrEmpty(validationContext.ProtocolMessage.Code)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21334)); } else { ValidateState(validationContext); } return; } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21332)); } // 'refresh_token' should not be returned from 'authorization_endpoint'. http://tools.ietf.org/html/rfc6749#section-4.2.2. if (!string.IsNullOrEmpty(validationContext.ProtocolMessage.RefreshToken)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21335)); } ValidateState(validationContext); ValidateIdToken(validationContext); ValidateNonce(validationContext); ValidateCHash(validationContext); ValidateAtHash(validationContext); }
/// <summary> /// Validates that the 'state' in message is valid. /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'state' to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ProtocolMessage ' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'validationContext.State' is present in <see cref="OpenIdConnectProtocolValidationContext.State"/> but either <see cref="OpenIdConnectProtocolValidationContext.ProtocolMessage"/> or its state property is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'state' in the context does not match the state in the message.</exception> protected virtual void ValidateState(OpenIdConnectProtocolValidationContext validationContext) { if (!RequireStateValidation) { LogHelper.LogVerbose(LogMessages.IDX21342); return; } if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21333)); } // if state is missing, but not required just return. Otherwise process it. if (!RequireState && string.IsNullOrEmpty(validationContext.State) && string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { LogHelper.LogInformation(LogMessages.IDX21341); return; } else if (string.IsNullOrEmpty(validationContext.State)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(LogHelper.FormatInvariant(LogMessages.IDX21329, LogHelper.MarkAsNonPII(RequireState)))); } else if (string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { // 'state' was sent, but message does not contain 'state' throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(LogHelper.FormatInvariant(LogMessages.IDX21330, LogHelper.MarkAsNonPII(RequireState)))); } if (!string.Equals(validationContext.State, validationContext.ProtocolMessage.State)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidStateException(LogHelper.FormatInvariant(LogMessages.IDX21331, validationContext.State, validationContext.ProtocolMessage.State))); } }
/// <summary> /// Validates that the 'state' in message is valid. /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'state' to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ProtocolMessage ' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'validationContext.State' is present in <see cref="OpenIdConnectProtocolValidationContext.State"/> but either <see cref="OpenIdConnectProtocolValidationContext.ProtocolMessage"/> or its state property is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidStateException">If 'state' in the context does not match the state in the message.</exception> protected virtual void ValidateState(OpenIdConnectProtocolValidationContext validationContext) { if (!RequireStateValidation) { IdentityModelEventSource.Logger.WriteVerbose(LogMessages.IDX10342); return; } if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ProtocolMessage == null) { throw LogHelper.LogException <OpenIdConnectProtocolException>(LogMessages.IDX10333); } // if state is missing, but not required just return. Otherwise process it. if (!RequireState && string.IsNullOrEmpty(validationContext.State) && string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { IdentityModelEventSource.Logger.WriteInformation(LogMessages.IDX10341); return; } else if (string.IsNullOrEmpty(validationContext.State)) { throw LogHelper.LogException <OpenIdConnectProtocolInvalidStateException>(LogMessages.IDX10329, RequireState); } else if (string.IsNullOrEmpty(validationContext.ProtocolMessage.State)) { // 'state' was sent, but message does not contain 'state' throw LogHelper.LogException <OpenIdConnectProtocolInvalidStateException>(LogMessages.IDX10330, RequireState.ToString()); } if (!string.Equals(validationContext.State, validationContext.ProtocolMessage.State, StringComparison.Ordinal)) { throw LogHelper.LogException <OpenIdConnectProtocolInvalidStateException>(LogMessages.IDX10331, validationContext.State, validationContext.ProtocolMessage.State); } }
/// <summary> /// Validates that the <see cref="JwtSecurityToken"/> contains the nonce. /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'nonce' to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is null and RequireNonce is true.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If the 'nonce' found in the 'id_token' does not match <see cref="OpenIdConnectProtocolValidationContext.Nonce"/>.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If <see cref="RequireTimeStampInNonce"/> is true and a timestamp is not: found, well formed, negatire or expired.</exception> /// <remarks>The timestamp is only validated if <see cref="RequireTimeStampInNonce"/> is true. /// <para>If <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is not-null, then a matching 'nonce' must exist in the 'id_token'.</para></remarks> protected virtual void ValidateNonce(OpenIdConnectProtocolValidationContext validationContext) { LogHelper.LogVerbose(LogMessages.IDX21319); if (validationContext == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext)); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException(nameof(validationContext.ValidatedIdToken)); } string nonceFoundInJwt = validationContext.ValidatedIdToken.Payload.Nonce; // if a nonce is not required AND there is no nonce in the context (which represents what was returned from the IDP) and the token log and return if (!RequireNonce && string.IsNullOrEmpty(validationContext.Nonce) && string.IsNullOrEmpty(nonceFoundInJwt)) { LogHelper.LogInformation(LogMessages.IDX21322); return; } // if we get here then RequireNonce == true || validationContext.None != null || nonceFoundInJwt != null if (string.IsNullOrEmpty(validationContext.Nonce) && string.IsNullOrEmpty(nonceFoundInJwt)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21320, RequireNonce))); } else if (string.IsNullOrEmpty(validationContext.Nonce)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21323, RequireNonce))); } else if (string.IsNullOrEmpty(nonceFoundInJwt)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21349, RequireNonce))); } if (!string.Equals(nonceFoundInJwt, validationContext.Nonce, StringComparison.Ordinal)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21321, validationContext.Nonce, nonceFoundInJwt, validationContext.ValidatedIdToken))); } if (RequireTimeStampInNonce) { int endOfTimestamp = nonceFoundInJwt.IndexOf('.'); if (endOfTimestamp == -1) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21325, nonceFoundInJwt))); } string timestamp = nonceFoundInJwt.Substring(0, endOfTimestamp); DateTime nonceTime = new DateTime(1979, 1, 1); // initializing to some value otherwise it gives an error long ticks = -1; try { ticks = Convert.ToInt64(timestamp, CultureInfo.InvariantCulture); } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21326, timestamp, nonceFoundInJwt), ex)); } if (ticks <= 0) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21326, timestamp, nonceFoundInJwt))); } try { nonceTime = DateTime.FromBinary(ticks); } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21327, timestamp, DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture), DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture)), ex)); } DateTime utcNow = DateTime.UtcNow; if (nonceTime + NonceLifetime < utcNow) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21324, nonceFoundInJwt, nonceTime.ToString(CultureInfo.InvariantCulture), utcNow.ToString(CultureInfo.InvariantCulture), NonceLifetime.ToString("c", CultureInfo.InvariantCulture)))); } } }
/// <summary> /// Validates the claims in the 'id_token' as per http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation /// </summary> /// <param name="validationContext">the <see cref="OpenIdConnectProtocolValidationContext"/> that contains expected values.</param> protected virtual void ValidateIdToken(OpenIdConnectProtocolValidationContext validationContext) { if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException("validationContext.ValidatedIdToken"); } // if user sets the custom validator, we call the delegate. The default checks for multiple audiences and azp are not executed. if (this.IdTokenValidator != null) { try { this.IdTokenValidator(validationContext.ValidatedIdToken, validationContext); } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21313, validationContext.ValidatedIdToken), ex)); } return; } else { JwtSecurityToken idToken = validationContext.ValidatedIdToken; // required claims if (idToken.Payload.Aud.Count == 0) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21314, JwtRegisteredClaimNames.Aud.ToLowerInvariant(), idToken))); } if (!idToken.Payload.Exp.HasValue) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21314, JwtRegisteredClaimNames.Exp.ToLowerInvariant(), idToken))); } if (!idToken.Payload.Iat.HasValue) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21314, JwtRegisteredClaimNames.Iat.ToLowerInvariant(), idToken))); } if (idToken.Payload.Iss == null) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21314, JwtRegisteredClaimNames.Iss.ToLowerInvariant(), idToken))); } // sub is required in OpenID spec; but we don't want to block valid idTokens provided by some identity providers if (RequireSub && (string.IsNullOrWhiteSpace(idToken.Payload.Sub))) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21314, JwtRegisteredClaimNames.Sub.ToLowerInvariant(), idToken))); } // optional claims if (RequireAcr && string.IsNullOrWhiteSpace(idToken.Payload.Acr)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21315, idToken))); } if (RequireAmr && idToken.Payload.Amr.Count == 0) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21316, idToken))); } if (RequireAuthTime && !(idToken.Payload.AuthTime.HasValue)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21317, idToken))); } if (RequireAzp && string.IsNullOrWhiteSpace(idToken.Payload.Azp)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21318, idToken))); } // if multiple audiences are present in the id_token, 'azp' claim should be present if (idToken.Payload.Aud.Count > 1 && string.IsNullOrEmpty(idToken.Payload.Azp)) { LogHelper.LogWarning(LogMessages.IDX21339); } // if 'azp' claim exist, it should be equal to 'client_id' of the application if (!string.IsNullOrEmpty(idToken.Payload.Azp)) { if (string.IsNullOrEmpty(validationContext.ClientId)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogMessages.IDX21308)); } else if (!string.Equals(idToken.Payload.Azp, validationContext.ClientId, StringComparison.Ordinal)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolException(LogHelper.FormatInvariant(LogMessages.IDX21340, idToken.Payload.Azp, validationContext.ClientId))); } } } }
/// <summary> /// Validates that the <see cref="JwtSecurityToken"/> contains the nonce. /// </summary> /// <param name="validationContext">A <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'nonce' to validate.</param> /// <exception cref="ArgumentNullException">If 'validationContext' is null.</exception> /// <exception cref="ArgumentNullException">If 'validationContext.ValidatedIdToken' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is null and RequireNonce is true.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If the 'nonce' found in the 'id_token' does not match <see cref="OpenIdConnectProtocolValidationContext.Nonce"/>.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">If <see cref="RequireTimeStampInNonce"/> is true and a timestamp is not: found, well formed, negatire or expired.</exception> /// <remarks>The timestamp is only validated if <see cref="RequireTimeStampInNonce"/> is true. /// <para>If <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is not-null, then a matching 'nonce' must exist in the 'id_token'.</para></remarks> protected virtual void ValidateNonce(OpenIdConnectProtocolValidationContext validationContext) { IdentityModelEventSource.Logger.WriteVerbose(LogMessages.IDX10319); if (validationContext == null) { throw LogHelper.LogArgumentNullException("validationContext"); } if (validationContext.ValidatedIdToken == null) { throw LogHelper.LogArgumentNullException("validationContext.ValidatedIdToken"); } string nonceFoundInJwt = validationContext.ValidatedIdToken.Payload.Nonce; if (!RequireNonce && string.IsNullOrEmpty(validationContext.Nonce) && string.IsNullOrEmpty(nonceFoundInJwt)) { IdentityModelEventSource.Logger.WriteInformation(LogMessages.IDX10322); return; } else if (string.IsNullOrEmpty(validationContext.Nonce)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10320, RequireNonce.ToString()))); } else if (string.IsNullOrEmpty(nonceFoundInJwt)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10323, RequireNonce.ToString(), validationContext.ValidatedIdToken))); } if (!string.Equals(nonceFoundInJwt, validationContext.Nonce, StringComparison.Ordinal)) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10321, validationContext.Nonce, nonceFoundInJwt, validationContext.ValidatedIdToken))); } if (RequireTimeStampInNonce) { int endOfTimestamp = nonceFoundInJwt.IndexOf('.'); if (endOfTimestamp == -1) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10325, validationContext.Nonce))); } string timestamp = nonceFoundInJwt.Substring(0, endOfTimestamp); DateTime nonceTime = new DateTime(1979, 1, 1); // initializing to some value otherwise it gives an error long ticks = -1; try { ticks = Convert.ToInt64(timestamp, CultureInfo.InvariantCulture); } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10326, timestamp, validationContext.Nonce), ex)); } if (ticks <= 0) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10326, timestamp, validationContext.Nonce))); } try { nonceTime = DateTime.FromBinary(ticks); } catch (Exception ex) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10327, timestamp, DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture), DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture)), ex)); } DateTime utcNow = DateTime.UtcNow; if (nonceTime + NonceLifetime < utcNow) { throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(String.Format(CultureInfo.InvariantCulture, LogMessages.IDX10324, validationContext.Nonce, nonceTime.ToString(), utcNow.ToString(), NonceLifetime.ToString()))); } } }