using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
///
/// An that can perform JWT-bearer based authentication.
///
public class JwtBearerHandler : AuthenticationHandler
{
private OpenIdConnectConfiguration? _configuration;
///
/// Initializes a new instance of .
///
///
public JwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
///
/// The handler calls methods on the events which give the application control at certain points where processing is occurring.
/// If it is not provided a default instance is supplied which does nothing when the methods are called.
///
protected new JwtBearerEvents Events
{
get => (JwtBearerEvents)base.Events!;
set => base.Events = value;
}
///
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new JwtBearerEvents());
///
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using set in the options.
///
///
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string? token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers.Authorization.ToString();
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
}
List? validationFailures = null;
SecurityToken? validatedToken = null;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
//ISecurityTokenValidator
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
Logger.TokenValidationFailed(ex);
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List(1);
}
validationFailures.Add(ex);
continue;
}
Logger.TokenValidationSucceeded();
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);
tokenValidatedContext.Properties.IssuedUtc = GetSafeDateTime(validatedToken.ValidFrom);
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result!;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token.");
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
private static DateTime? GetSafeDateTime(DateTime dateTime)
{
// Assigning DateTime.MinValue or default(DateTime) to a DateTimeOffset when in a UTC+X timezone will throw
// Since we don't really care about DateTime.MinValue in this case let's just set the field to null
if (dateTime == DateTime.MinValue)
{
return null;
}
return dateTime;
}
///
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
{
AuthenticateFailure = authResult?.Failure
};
// Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
{
eventContext.Error = "invalid_token";
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
}
await Events.Challenge(eventContext);
if (eventContext.Handled)
{
return;
}
Response.StatusCode = 401;
if (string.IsNullOrEmpty(eventContext.Error) &&
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
string.IsNullOrEmpty(eventContext.ErrorUri))
{
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
}
else
{
// https://tools.ietf.org/html/rfc6750#section-3.1
// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
var builder = new StringBuilder(Options.Challenge);
if (Options.Challenge.IndexOf(' ') > 0)
{
// Only add a comma after the first param, if any
builder.Append(',');
}
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(" error=\"");
builder.Append(eventContext.Error);
builder.Append('\"');
}
if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(',');
}
builder.Append(" error_description=\"");
builder.Append(eventContext.ErrorDescription);
builder.Append('\"');
}
if (!string.IsNullOrEmpty(eventContext.ErrorUri))
{
if (!string.IsNullOrEmpty(eventContext.Error) ||
!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
builder.Append(',');
}
builder.Append(" error_uri=\"");
builder.Append(eventContext.ErrorUri);
builder.Append('\"');
}
Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
}
}
///
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
var forbiddenContext = new ForbiddenContext(Context, Scheme, Options);
Response.StatusCode = 403;
return Events.Forbidden(forbiddenContext);
}
private static string CreateErrorDescription(Exception authFailure)
{
IReadOnlyCollection exceptions;
if (authFailure is AggregateException agEx)
{
exceptions = agEx.InnerExceptions;
}
else
{
exceptions = new[] { authFailure };
}
var messages = new List<string>(exceptions.Count);
foreach (var ex in exceptions)
{
// Order sensitive, some of these exceptions derive from others
// and we want to display the most specific message possible.
switch (ex)
{
case SecurityTokenInvalidAudienceException stia:
messages.Add($"The audience '{stia.InvalidAudience ?? "(null)"}' is invalid");
break;
case SecurityTokenInvalidIssuerException stii:
messages.Add($"The issuer '{stii.InvalidIssuer ?? "(null)"}' is invalid");
break;
case SecurityTokenNoExpirationException _:
messages.Add("The token has no expiration");
break;
case SecurityTokenInvalidLifetimeException stil:
messages.Add("The token lifetime is invalid; NotBefore: "
+ $"'{stil.NotBefore?.ToString(CultureInfo.InvariantCulture) ?? "(null)"}'"
+ $", Expires: '{stil.Expires?.ToString(CultureInfo.InvariantCulture) ?? "(null)"}'");
break;
case SecurityTokenNotYetValidException stnyv:
messages.Add($"The token is not valid before '{stnyv.NotBefore.ToString(CultureInfo.InvariantCulture)}'");
break;
case SecurityTokenExpiredException ste:
messages.Add($"The token expired at '{ste.Expires.ToString(CultureInfo.InvariantCulture)}'");
break;
case SecurityTokenSignatureKeyNotFoundException _:
messages.Add("The signature key was not found");
break;
case SecurityTokenInvalidSignatureException _:
messages.Add("The signature is invalid");
break;
}
}
return string.Join("; ", messages);
}
}
}
using System;
using System.Security.Claims;
namespace Microsoft.IdentityModel.Tokens
{
///
/// ISecurityTokenValidator
///
public interface ISecurityTokenValidator
{
///
/// Returns true if the token can be read, false otherwise.
///
bool CanReadToken(string securityToken);
///
/// Returns true if a token can be validated.
///
bool CanValidateToken { get; }
///
/// Gets and sets the maximum size in bytes, that a will be processed.
///
Int32 MaximumTokenSizeInBytes { get; set; }
///
/// Validates a token passed as a string using
///
ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken);
}
}
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.JsonWebTokens;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
namespace System.IdentityModel.Tokens.Jwt
{
///
/// A designed for creating and validating Json Web Tokens. See: http://tools.ietf.org/html/rfc7519 and http://www.rfc-editor.org/info/rfc7515
///
public class JwtSecurityTokenHandler : SecurityTokenHandler
{
private delegate bool CertMatcher(X509Certificate2 cert);
private ISet<string> _inboundClaimFilter;
private IDictionary<string, string> _inboundClaimTypeMap;
private static string _jsonClaimType = _namespace + "/json_type";
private const string _namespace = "http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties";
private IDictionary<string, string> _outboundClaimTypeMap;
private IDictionary<string, string> _outboundAlgorithmMap = null;
private static string _shortClaimType = _namespace + "/ShortTypeName";
private bool _mapInboundClaims = DefaultMapInboundClaims;
///
/// Default claim type mapping for inbound claims.
///
public static IDictionary<string, string> DefaultInboundClaimTypeMap = ClaimTypeMapping.InboundClaimTypeMap;
///
/// Default value for the flag that determines whether or not the InboundClaimTypeMap is used.
///
public static bool DefaultMapInboundClaims = true;
///
/// Default claim type mapping for outbound claims.
///
public static IDictionary<string, string> DefaultOutboundClaimTypeMap = ClaimTypeMapping.OutboundClaimTypeMap;
///
/// Default claim type filter list.
///
public static ISet<string> DefaultInboundClaimFilter = ClaimTypeMapping.InboundClaimFilter;
///
/// Default JwtHeader algorithm mapping
///
public static IDictionary<string, string> DefaultOutboundAlgorithmMap = new Dictionary<string, string>
{
{ SecurityAlgorithms.EcdsaSha256Signature, SecurityAlgorithms.EcdsaSha256 },
{ SecurityAlgorithms.EcdsaSha384Signature, SecurityAlgorithms.EcdsaSha384 },
{ SecurityAlgorithms.EcdsaSha512Signature, SecurityAlgorithms.EcdsaSha512 },
{ SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.HmacSha256 },
{ SecurityAlgorithms.HmacSha384Signature, SecurityAlgorithms.HmacSha384 },
{ SecurityAlgorithms.HmacSha512Signature, SecurityAlgorithms.HmacSha512 },
{ SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.RsaSha256 },
{ SecurityAlgorithms.RsaSha384Signature, SecurityAlgorithms.RsaSha384 },
{ SecurityAlgorithms.RsaSha512Signature, SecurityAlgorithms.RsaSha512 },
};
///
/// Static initializer for a new object. Static initializers run before the first instance of the type is created.
///
static JwtSecurityTokenHandler()
{
LogHelper.LogVerbose("Assembly version info: " + typeof(JwtSecurityTokenHandler).AssemblyQualifiedName);
}
///
/// Initializes a new instance of the class.
///
public JwtSecurityTokenHandler()
{
if (_mapInboundClaims)
_inboundClaimTypeMap = new Dictionary<string, string>(DefaultInboundClaimTypeMap);
else
_inboundClaimTypeMap = new Dictionary<string, string>();
_outboundClaimTypeMap = new Dictionary<string, string>(DefaultOutboundClaimTypeMap);
_inboundClaimFilter = new HashSet<string>(DefaultInboundClaimFilter);
_outboundAlgorithmMap = new Dictionary<string, string>(DefaultOutboundAlgorithmMap);
}
///
/// Gets or sets the property which is used when determining whether or not to map claim types that are extracted when validating a .
/// If this is set to true, the is set to the JSON claim 'name' after translating using this mapping. Otherwise, no mapping occurs.
/// The default value is true.
///
public bool MapInboundClaims
{
get
{
return _mapInboundClaims;
}
set
{
// If the inbound claim type mapping was turned off and is being turned on for the first time, make sure that the _inboundClaimTypeMap is populated with the default mappings.
if (!_mapInboundClaims && value && _inboundClaimTypeMap.Count == 0)
_inboundClaimTypeMap = new Dictionary<string, string>(DefaultInboundClaimTypeMap);
_mapInboundClaims = value;
}
}
///
/// Gets or sets the which is used when setting the for claims in the extracted when validating a .
/// The is set to the JSON claim 'name' after translating using this mapping.
/// The default value is ClaimTypeMapping.InboundClaimTypeMap.
///
/// 'value' is null.
public IDictionary<string, string> InboundClaimTypeMap
{
get
{
return _inboundClaimTypeMap;
}
set
{
_inboundClaimTypeMap = value ?? throw LogHelper.LogArgumentNullException(nameof(value));
}
}
///
/// Gets or sets the which is used when creating a from (s).
/// The JSON claim 'name' value is set to after translating using this mapping.
/// The default value is ClaimTypeMapping.OutboundClaimTypeMap
///
/// This mapping is applied only when using or . Adding values directly will not result in translation.
/// 'value' is null.
public IDictionary<string, string> OutboundClaimTypeMap
{
get
{
return _outboundClaimTypeMap;
}
set
{
if (value == null)
throw LogHelper.LogArgumentNullException(nameof(value));
_outboundClaimTypeMap = value;
}
}
///
/// Gets the outbound algorithm map that is passed to the constructor.
///
public IDictionary<string, string> OutboundAlgorithmMap
{
get
{
return _outboundAlgorithmMap;
}
}
/// Gets or sets the used to filter claims when populating a claims form a .
/// When a is validated, claims with types found in this will not be added to the .
/// The default value is ClaimTypeMapping.InboundClaimFilter.
///
/// 'value' is null.
public ISet<string> InboundClaimFilter
{
get
{
return _inboundClaimFilter;
}
set
{
if (value == null)
throw LogHelper.LogArgumentNullException(nameof(value));
_inboundClaimFilter = value;
}
}
///
/// Gets or sets the property name of the will contain the original JSON claim 'name' if a mapping occurred when the (s) were created.
/// See for more information.
///
/// If .IsNullOrWhiteSpace('value') is true.
public static string ShortClaimTypeProperty
{
get
{
return _shortClaimType;
}
set
{
if (string.IsNullOrWhiteSpace(value))
throw LogHelper.LogArgumentNullException(nameof(value));
_shortClaimType = value;
}
}
///
/// Gets or sets the property name of the will contain .Net type that was recognized when JwtPayload.Claims serialized the value to JSON.
/// See for more information.
///
/// If .IsNullOrWhiteSpace('value') is true.
public static string JsonClaimTypeProperty
{
get
{
return _jsonClaimType;
}
set
{
if (string.IsNullOrWhiteSpace(value))
throw LogHelper.LogArgumentNullException(nameof(value));
_jsonClaimType = value;
}
}
///
/// Returns a value that indicates if this handler can validate a .
///
/// 'true', indicating this instance can validate a .
public override bool CanValidateToken
{
get { return true; }
}
///
/// Gets the value that indicates if this instance can write a .
///
/// 'true', indicating this instance can write a .
public override bool CanWriteToken
{
get { return true; }
}
///
/// Gets the type of the .
///
/// The type of
public override Type TokenType
{
get { return typeof(JwtSecurityToken); }
}
///
/// Determines if the string is a well formed Json Web Token (JWT).
/// see: http://tools.ietf.org/html/rfc7519
///
/// String that should represent a valid JWT.
/// Uses matching one of:
/// JWS: @"^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$"
/// JWE: (dir): @"^[A-Za-z0-9-_]+\.\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$"
/// JWE: (wrappedkey): @"^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]$"
///
///
/// 'false' if the token is null or whitespace.
/// 'false' if token.Length is greater than .
/// 'true' if the token is in JSON compact serialization format.
///
public override bool CanReadToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
return false;
if (token.Length > MaximumTokenSizeInBytes)
{
LogHelper.LogInformation(TokenLogMessages.IDX10209, token.Length, MaximumTokenSizeInBytes);
return false;
}
// Set the maximum number of segments to MaxJwtSegmentCount + 1. This controls the number of splits and allows detecting the number of segments is too large.
// For example: "a.b.c.d.e.f.g.h" => [a], [b], [c], [d], [e], [f.g.h]. 6 segments.
// If just MaxJwtSegmentCount was used, then [a], [b], [c], [d], [e.f.g.h] would be returned. 5 segments.
string[] tokenParts = token.Split(new char[] { '.' }, JwtConstants.MaxJwtSegmentCount + 1);
if (tokenParts.Length == JwtConstants.JwsSegmentCount)
{
return JwtTokenUtilities.RegexJws.IsMatch(token);
}
else if (tokenParts.Length == JwtConstants.JweSegmentCount)
{
return JwtTokenUtilities.RegexJwe.IsMatch(token);
}
LogHelper.LogInformation(LogMessages.IDX12720);
return false;
}
///
/// Returns a Json Web Token (JWT).
///
/// A that contains details of contents of the token.
/// A JWS and JWE can be returned.
/// If is provided, then a JWE will be created.
/// If is provided then a JWS will be created.
/// If both are provided then a JWE with an embedded JWS will be created.
///
public virtual string CreateEncodedJwt(SecurityTokenDescriptor tokenDescriptor)
{
if (tokenDescriptor == null)
throw LogHelper.LogArgumentNullException(nameof(tokenDescriptor));
return CreateJwtSecurityToken(tokenDescriptor).RawData;
}
///
/// Creates a JWT in 'Compact Serialization Format'.
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// The notbefore time for this token.
/// The expiration time for this token.
/// The issue time for this token.
/// Contains cryptographic material for generating a signature.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. See for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each in the will map by applying . Modifying could change the outbound JWT.
/// If is provided, then a JWS will be created.
///
/// A Base64UrlEncoded string in 'Compact Serialization Format'.
public virtual string CreateEncodedJwt(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, null, null, null).RawData;
}
///
/// Creates a JWT in 'Compact Serialization Format'.
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// Translated into 'epoch time' and assigned to 'nbf'.
/// Translated into 'epoch time' and assigned to 'exp'.
/// Translated into 'epoch time' and assigned to 'iat'.
/// Contains cryptographic material for signing.
/// Contains cryptographic material for encrypting.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each in the will map by applying . Modifying could change the outbound JWT.
///
/// A Base64UrlEncoded string in 'Compact Serialization Format'.
/// If 'expires' <= 'notBefore'.
public virtual string CreateEncodedJwt(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, encryptingCredentials, null, null).RawData;
}
///
/// Creates a JWT in 'Compact Serialization Format'.
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// Translated into 'epoch time' and assigned to 'nbf'.
/// Translated into 'epoch time' and assigned to 'exp'.
/// Translated into 'epoch time' and assigned to 'iat'.
/// Contains cryptographic material for signing.
/// Contains cryptographic material for encrypting.
/// A collection of (key,value) pairs representing (s) for this token.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each in the will map by applying . Modifying could change the outbound JWT.
///
/// A Base64UrlEncoded string in 'Compact Serialization Format'.
/// If 'expires' <= 'notBefore'.
public virtual string CreateEncodedJwt(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary<string, object> claimCollection)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, encryptingCredentials, claimCollection, null).RawData;
}
///
/// Creates a Json Web Token (JWT).
///
/// A that contains details of contents of the token.
/// is used to sign .
public virtual JwtSecurityToken CreateJwtSecurityToken(SecurityTokenDescriptor tokenDescriptor)
{
if (tokenDescriptor == null)
throw LogHelper.LogArgumentNullException(nameof(tokenDescriptor));
return CreateJwtSecurityTokenPrivate(
tokenDescriptor.Issuer,
tokenDescriptor.Audience,
tokenDescriptor.Subject,
tokenDescriptor.NotBefore,
tokenDescriptor.Expires,
tokenDescriptor.IssuedAt,
tokenDescriptor.SigningCredentials,
tokenDescriptor.EncryptingCredentials,
tokenDescriptor.Claims,
tokenDescriptor.TokenType);
}
///
/// Creates a
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// The notbefore time for this token.
/// The expiration time for this token.
/// The issue time for this token.
/// Contains cryptographic material for generating a signature.
/// Contains cryptographic material for encrypting the token.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each on the added will have translated according to the mapping found in
/// . Adding and removing to will affect the name component of the Json claim.
/// is used to sign .
/// is used to encrypt or .
///
/// A .
/// If 'expires' <= 'notBefore'.
public virtual JwtSecurityToken CreateJwtSecurityToken(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, encryptingCredentials, null, null);
}
///
/// Creates a
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// The notbefore time for this token.
/// The expiration time for this token.
/// The issue time for this token.
/// Contains cryptographic material for generating a signature.
/// Contains cryptographic material for encrypting the token.
/// A collection of (key,value) pairs representing (s) for this token.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each on the added will have translated according to the mapping found in
/// . Adding and removing to will affect the name component of the Json claim.
/// is used to sign .
/// is used to encrypt or .
///
/// A .
/// If 'expires' <= 'notBefore'.
public virtual JwtSecurityToken CreateJwtSecurityToken(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary<string, object> claimCollection)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, encryptingCredentials, claimCollection, null);
}
///
/// Creates a
///
/// The issuer of the token.
/// The audience for this token.
/// The source of the (s) for this token.
/// The notbefore time for this token.
/// The expiration time for this token.
/// The issue time for this token.
/// Contains cryptographic material for generating a signature.
/// If is not null, then a claim { actort, 'value' } will be added to the payload. for details on how the value is created.
/// See for details on how the HeaderParameters are added to the header.
/// See for details on how the values are added to the payload.
/// Each on the added will have translated according to the mapping found in
/// . Adding and removing to will affect the name component of the Json claim.
/// is used to sign .
///
/// A .
/// If 'expires' <= 'notBefore'.
public virtual JwtSecurityToken CreateJwtSecurityToken(string issuer = null, string audience = null, ClaimsIdentity subject = null, DateTime? notBefore = null, DateTime? expires = null, DateTime? issuedAt = null, SigningCredentials signingCredentials = null)
{
return CreateJwtSecurityTokenPrivate(issuer, audience, subject, notBefore, expires, issuedAt, signingCredentials, null, null, null);
}
///
/// Creates a Json Web Token (JWT).
///
/// A that contains details of contents of the token.
/// is used to sign .
public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor)
{
if (tokenDescriptor == null)
throw LogHelper.LogArgumentNullException(nameof(tokenDescriptor));
return CreateJwtSecurityTokenPrivate(
tokenDescriptor.Issuer,
tokenDescriptor.Audience,
tokenDescriptor.Subject,
tokenDescriptor.NotBefore,
tokenDescriptor.Expires,
tokenDescriptor.IssuedAt,
tokenDescriptor.SigningCredentials,
tokenDescriptor.EncryptingCredentials,
tokenDescriptor.Claims,
tokenDescriptor.TokenType);
}
private JwtSecurityToken CreateJwtSecurityTokenPrivate(string issuer, string audience, ClaimsIdentity subject, DateTime? notBefore, DateTime? expires, DateTime? issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, IDictionary<string, object> claimCollection, string tokenType)
{
if (SetDefaultTimesOnTokenCreation && (!expires.HasValue || !issuedAt.HasValue || !notBefore.HasValue))
{
DateTime now = DateTime.UtcNow;
if (!expires.HasValue)
expires = now + TimeSpan.FromMinutes(TokenLifetimeInMinutes);
if (!issuedAt.HasValue)
issuedAt = now;
if (!notBefore.HasValue)
notBefore = now;
}
LogHelper.LogVerbose(LogMessages.IDX12721, (audience ?? "null"), (issuer ?? "null"));
JwtPayload payload = new JwtPayload(issuer, audience, (subject == null ? null : OutboundClaimTypeTransform(subject.Claims)), (claimCollection == null ? null : OutboundClaimTypeTransform(claimCollection)), notBefore, expires, issuedAt);
JwtHeader header = new JwtHeader(signingCredentials, OutboundAlgorithmMap, tokenType);
if (subject?.Actor != null)
payload.AddClaim(new Claim(JwtRegisteredClaimNames.Actort, CreateActorValue(subject.Actor)));
string rawHeader = header.Base64UrlEncode();
string rawPayload = payload.Base64UrlEncode();
string rawSignature = signingCredentials == null ? string.Empty : JwtTokenUtilities.CreateEncodedSignature(string.Concat(rawHeader, ".", rawPayload), signingCredentials);
LogHelper.LogInformation(LogMessages.IDX12722, rawHeader, rawPayload, rawSignature);
if (encryptingCredentials != null)
return EncryptToken(new JwtSecurityToken(header, payload, rawHeader, rawPayload, rawSignature), encryptingCredentials, tokenType);
return new JwtSecurityToken(header, payload, rawHeader, rawPayload, rawSignature);
}
private JwtSecurityToken EncryptToken(JwtSecurityToken innerJwt, EncryptingCredentials encryptingCredentials, string tokenType)
{
if (encryptingCredentials == null)
throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
var cryptoProviderFactory = encryptingCredentials.CryptoProviderFactory ?? encryptingCredentials.Key.CryptoProviderFactory;
if (cryptoProviderFactory == null)
throw LogHelper.LogExceptionMessage(new ArgumentException(TokenLogMessages.IDX10620));
byte[] wrappedKey = null;
SecurityKey securityKey = JwtTokenUtilities.GetSecurityKey(encryptingCredentials, cryptoProviderFactory, out wrappedKey);
using (var encryptionProvider = cryptoProviderFactory.CreateAuthenticatedEncryptionProvider(securityKey, encryptingCredentials.Enc))
{
if (encryptionProvider == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogMessages.IDX12730));
try
{
var header = new JwtHeader(encryptingCredentials, OutboundAlgorithmMap, tokenType);
var encryptionResult = encryptionProvider.Encrypt(Encoding.UTF8.GetBytes(innerJwt.RawData), Encoding.ASCII.GetBytes(header.Base64UrlEncode()));
return JwtConstants.DirectKeyUseAlg.Equals(encryptingCredentials.Alg, StringComparison.Ordinal) ? new JwtSecurityToken(
header,
innerJwt,
header.Base64UrlEncode(),
string.Empty,
Base64UrlEncoder.Encode(encryptionResult.IV),
Base64UrlEncoder.Encode(encryptionResult.Ciphertext),
Base64UrlEncoder.Encode(encryptionResult.AuthenticationTag)) :
new JwtSecurityToken(
header,
innerJwt,
header.Base64UrlEncode(),
Base64UrlEncoder.Encode(wrappedKey),
Base64UrlEncoder.Encode(encryptionResult.IV),
Base64UrlEncoder.Encode(encryptionResult.Ciphertext),
Base64UrlEncoder.Encode(encryptionResult.AuthenticationTag));
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogHelper.FormatInvariant(TokenLogMessages.IDX10616, encryptingCredentials.Enc, encryptingCredentials.Key), ex));
}
}
}
private IEnumerable OutboundClaimTypeTransform(IEnumerable claims)
{
foreach (Claim claim in claims)
{
string type = null;
if (_outboundClaimTypeMap.TryGetValue(claim.Type, out type))
{
yield return new Claim(type, claim.Value, claim.ValueType, claim.Issuer, claim.OriginalIssuer, claim.Subject);
}
else
{
yield return claim;
}
}
}
private IDictionary<string, object> OutboundClaimTypeTransform(IDictionary<string, object> claimCollection)
{
var claims = new Dictionary<string, object>();
foreach (string claimType in claimCollection.Keys)
{
if (_outboundClaimTypeMap.TryGetValue(claimType, out string type))
claims[type] = claimCollection[claimType];
else
claims[claimType] = claimCollection[claimType];
}
return claims;
}
///
/// Converts a string into an instance of .
///
/// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
/// A
/// 'token' is null or empty.
/// 'token.Length' is greater than .
///
/// If the 'token' is in JWE Compact Serialization format, only the protected header will be deserialized.
/// This method is unable to decrypt the payload. Use to obtain the payload.
public JwtSecurityToken ReadJwtToken(string token)
{
if (string.IsNullOrEmpty(token))
throw LogHelper.LogArgumentNullException(nameof(token));
if (token.Length > MaximumTokenSizeInBytes)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, token.Length, MaximumTokenSizeInBytes)));
if (!CanReadToken(token))
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX12709, token)));
var jwtToken = new JwtSecurityToken();
jwtToken.Decode(token.Split('.'), token);
return jwtToken;
}
///
/// Converts a string into an instance of .
///
/// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
/// A
/// 'token' is null or empty.
/// 'token.Length' is greater than .
///
/// If the 'token' is in JWE Compact Serialization format, only the protected header will be deserialized.
/// This method is unable to decrypt the payload. Use to obtain the payload.
public override SecurityToken ReadToken(string token)
{
return ReadJwtToken(token);
}
///
/// Deserializes token with the provided .
///
/// .
/// The current .
/// The
/// This method is not current supported.
public override SecurityToken ReadToken(XmlReader reader, TokenValidationParameters validationParameters)
{
throw new NotImplementedException();
}
///
/// Reads and validates a 'JSON Web Token' (JWT) encoded as a JWS or JWE in Compact Serialized Format.
///
/// the JWT encoded as JWE or JWS
/// Contains validation parameters for the .
/// The that was validated.
/// is null or whitespace.
/// is null.
/// .Length is greater than .
/// does not have 3 or 5 parts.
/// returns false.
/// was a JWE was not able to be decrypted.
/// 'kid' header claim is not null AND decryption fails.
/// 'enc' header claim is null or empty.
/// 'exp' claim is < DateTime.UtcNow.
/// is null or whitespace and is null. Audience is not validated if is set to false.
/// 'aud' claim did not match either or one of .
/// 'nbf' claim is > 'exp' claim.
/// .signature is not properly formatted.
/// 'exp' claim is missing and is true.
/// is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time.
/// 'nbf' claim is > DateTime.UtcNow.
/// could not be added to the .
/// is found in the cache.
/// A from the JWT. Does not include claims found in the JWT header.
///
/// Many of the exceptions listed above are not thrown directly from this method. See to examine the call graph.
///
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
if (string.IsNullOrWhiteSpace(token))
throw LogHelper.LogArgumentNullException(nameof(token));
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
if (token.Length > MaximumTokenSizeInBytes)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, token.Length, MaximumTokenSizeInBytes)));
var tokenParts = token.Split(new char[] { '.' }, JwtConstants.MaxJwtSegmentCount + 1);
if (tokenParts.Length != JwtConstants.JwsSegmentCount && tokenParts.Length != JwtConstants.JweSegmentCount)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX12741, token)));
ClaimsPrincipal claimsPrincipal = null;
SecurityToken signatureValidatedToken = null;
if (tokenParts.Length == JwtConstants.JweSegmentCount)
{
var jwtToken = ReadJwtToken(token);
var decryptedJwt = DecryptToken(jwtToken, validationParameters);
var innerToken = ValidateSignature(decryptedJwt, validationParameters);
jwtToken.InnerToken = innerToken;
signatureValidatedToken = jwtToken;
claimsPrincipal = ValidateTokenPayload(innerToken, validationParameters);
}
else
{
signatureValidatedToken = ValidateSignature(token, validationParameters);
claimsPrincipal = ValidateTokenPayload(signatureValidatedToken as JwtSecurityToken, validationParameters);
}
validatedToken = signatureValidatedToken;
return claimsPrincipal;
}
///
/// Validates the JSON payload of a .
///
/// The token to validate.
/// Contains validation parameters for the .
/// A from the jwt. Does not include the header claims.
protected ClaimsPrincipal ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
if (jwtToken is null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
if (validationParameters is null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
DateTime? expires = (jwtToken.Payload.Exp == null) ? null : new DateTime?(jwtToken.ValidTo);
DateTime? notBefore = (jwtToken.Payload.Nbf == null) ? null : new DateTime?(jwtToken.ValidFrom);
ValidateLifetime(notBefore, expires, jwtToken, validationParameters);
ValidateAudience(jwtToken.Audiences, jwtToken, validationParameters);
string issuer = ValidateIssuer(jwtToken.Issuer, jwtToken, validationParameters);
ValidateTokenReplay(expires, jwtToken.RawData, validationParameters);
if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jwtToken.Actor))
{
ValidateToken(jwtToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters, out _);
}
ValidateIssuerSecurityKey(jwtToken.SigningKey, jwtToken, validationParameters);
Validators.ValidateTokenType(jwtToken.Header.Typ, jwtToken, validationParameters);
var identity = CreateClaimsIdentity(jwtToken, issuer, validationParameters);
if (validationParameters.SaveSigninToken)
identity.BootstrapContext = jwtToken.RawData;
LogHelper.LogInformation(TokenLogMessages.IDX10241, jwtToken.RawData);
return new ClaimsPrincipal(identity);
}
///
/// Serializes a into a JWT in Compact Serialization Format.
///
/// to serialize.
///
/// The JWT will be serialized as a JWE or JWS.
/// will be used to create the JWT. If there is an inner token, the inner token's payload will be used.
/// If either or .SigningCredentials are set, the JWT will be signed.
/// If is set, a JWE will be created using the JWT above as the plaintext.
///
/// 'token' is null.
/// 'token' is not a not .
/// both and are set.
/// both and .EncryptingCredentials are set.
/// if is set and is not set.
/// A JWE or JWS in 'Compact Serialization Format'.
public override string WriteToken(SecurityToken token)
{
if (token == null)
throw LogHelper.LogArgumentNullException(nameof(token));
JwtSecurityToken jwtToken = token as JwtSecurityToken;
if (jwtToken == null)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX12706, GetType(), typeof(JwtSecurityToken), token.GetType()), nameof(token)));
var encodedPayload = jwtToken.EncodedPayload;
var encodedSignature = string.Empty;
var encodedHeader = string.Empty;
if (jwtToken.InnerToken != null)
{
if (jwtToken.SigningCredentials != null)
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogMessages.IDX12736));
if (jwtToken.InnerToken.Header.EncryptingCredentials != null)
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogMessages.IDX12737));
if (jwtToken.Header.EncryptingCredentials == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenEncryptionFailedException(LogMessages.IDX12735));
if (jwtToken.InnerToken.SigningCredentials != null)
encodedSignature = JwtTokenUtilities.CreateEncodedSignature(string.Concat(jwtToken.InnerToken.EncodedHeader, ".", jwtToken.EncodedPayload), jwtToken.InnerToken.SigningCredentials);
return EncryptToken(new JwtSecurityToken(jwtToken.InnerToken.Header, jwtToken.InnerToken.Payload, jwtToken.InnerToken.EncodedHeader, encodedPayload, encodedSignature), jwtToken.EncryptingCredentials, jwtToken.InnerToken.Header.Typ).RawData;
}
// if EncryptingCredentials isn't set, then we need to create JWE
// first create a new header with the SigningCredentials, Create a JWS then wrap it in a JWE
var header = jwtToken.EncryptingCredentials == null ? jwtToken.Header : new JwtHeader(jwtToken.SigningCredentials);
encodedHeader = header.Base64UrlEncode();
if (jwtToken.SigningCredentials != null)
encodedSignature = JwtTokenUtilities.CreateEncodedSignature(string.Concat(encodedHeader, ".", encodedPayload), jwtToken.SigningCredentials);
if (jwtToken.EncryptingCredentials != null)
return EncryptToken(new JwtSecurityToken(header, jwtToken.Payload, encodedHeader, encodedPayload, encodedSignature), jwtToken.EncryptingCredentials, jwtToken.Header.Typ).RawData;
else
return string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
}
///
/// Obtains a and validates the signature.
///
/// Bytes to validate.
/// Signature to compare against.
/// to use.
/// Crypto algorithm to use.
/// The being validated.
/// Priority will be given to over .
/// 'true' if signature is valid.
private static bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityKey key, string algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateAlgorithm(algorithm, key, securityToken, validationParameters);
var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, algorithm);
if (signatureProvider == null)
throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), algorithm ?? "Null")));
try
{
return signatureProvider.Verify(encodedBytes, signature);
}
finally
{
cryptoProviderFactory.ReleaseSignatureProvider(signatureProvider);
}
}
///
/// Validates that the signature, if found or required, is valid.
///
/// A JWS token.
/// that contains signing keys.
/// If 'jwt' is null or whitespace.
/// If 'validationParameters' is null.
/// If a signature is not found and is true.
///
/// If the 'token' has a key identifier and none of the (s) provided result in a validated signature.
/// This can indicate that a key refresh is required.
///
///
/// If the 'token' has a key identifier and none of the (s) provided result in a validated signature as well as the token
/// had validation errors or lifetime or issuer. This is not intended to be a signal to refresh keys.
///
/// If after trying all the (s), none result in a validated signature AND the 'token' does not have a key identifier.
/// A that has the signature validated if token was signed.
/// If the 'token' is signed, the signature is validated even if is false.
/// If the 'token' signature is validated, then the will be set to the key that signed the 'token'.It is the responsibility of to set the
protected virtual JwtSecurityToken ValidateSignature(string token, TokenValidationParameters validationParameters)
{
if (string.IsNullOrWhiteSpace(token))
throw LogHelper.LogArgumentNullException(nameof(token));
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
if (validationParameters.SignatureValidator != null)
{
var validatedJwtToken = validationParameters.SignatureValidator(token, validationParameters);
if (validatedJwtToken == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, token)));
var validatedJwt = validatedJwtToken as JwtSecurityToken;
if (validatedJwt == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, typeof(JwtSecurityToken), validatedJwtToken.GetType(), token)));
return validatedJwt;
}
JwtSecurityToken jwtToken = null;
if (validationParameters.TokenReader != null)
{
var securityToken = validationParameters.TokenReader(token, validationParameters);
if (securityToken == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10510, token)));
jwtToken = securityToken as JwtSecurityToken;
if (jwtToken == null)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10509, typeof(JwtSecurityToken), securityToken.GetType(), token)));
}
else
{
jwtToken = ReadJwtToken(token);
}
byte[] encodedBytes = Encoding.UTF8.GetBytes(jwtToken.RawHeader + "." + jwtToken.RawPayload);
if (string.IsNullOrEmpty(jwtToken.RawSignature))
{
if (validationParameters.RequireSignedTokens)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10504, token)));
else
return jwtToken;
}
bool kidMatched = false;
IEnumerable keys = null;
if (validationParameters.IssuerSigningKeyResolver != null)
{
keys = validationParameters.IssuerSigningKeyResolver(token, jwtToken, jwtToken.Header.Kid, validationParameters);
}
else
{
var key = ResolveIssuerSigningKey(token, jwtToken, validationParameters);
if (key != null)
{
kidMatched = true;
keys = new List { key };
}
}
if (keys == null && validationParameters.TryAllIssuerSigningKeys)
{
// control gets here if:
// 1. User specified delegate: IssuerSigningKeyResolver returned null
// 2. ResolveIssuerSigningKey returned null
// Try all the keys. This is the degenerate case, not concerned about perf.
keys = TokenUtilities.GetAllSigningKeys(validationParameters);
}
// keep track of exceptions thrown, keys that were tried
var exceptionStrings = new StringBuilder();
var keysAttempted = new StringBuilder();
bool kidExists = !string.IsNullOrEmpty(jwtToken.Header.Kid);
byte[] signatureBytes;
try
{
signatureBytes = Base64UrlEncoder.DecodeBytes(jwtToken.RawSignature);
}
catch (FormatException e)
{
throw new SecurityTokenInvalidSignatureException(TokenLogMessages.IDX10508, e);
}
if (keys != null)
{
foreach (var key in keys)
{
try
{
if (ValidateSignature(encodedBytes, signatureBytes, key, jwtToken.Header.Alg, jwtToken, validationParameters))
{
LogHelper.LogInformation(TokenLogMessages.IDX10242, token);
jwtToken.SigningKey = key;
return jwtToken;
}
}
catch (Exception ex)
{
exceptionStrings.AppendLine(ex.ToString());
}
if (key != null)
{
keysAttempted.AppendLine(key.ToString() + " , KeyId: " + key.KeyId);
if (kidExists && !kidMatched && key.KeyId != null)
kidMatched = jwtToken.Header.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
}
}
if (kidExists)
{
if (kidMatched)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511, keysAttempted, jwtToken.Header.Kid, exceptionStrings, jwtToken)));
DateTime? expires = (jwtToken.Payload.Exp == null) ? null : new DateTime?(jwtToken.ValidTo);
DateTime? notBefore = (jwtToken.Payload.Nbf == null) ? null : new DateTime?(jwtToken.ValidFrom);
InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(
jwtToken,
notBefore,
expires,
jwtToken.Header.Kid,
validationParameters,
exceptionStrings);
}
if (keysAttempted.Length > 0)
throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10503, keysAttempted, exceptionStrings, jwtToken)));
throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(TokenLogMessages.IDX10500));
}
private static IEnumerable GetAllDecryptionKeys(TokenValidationParameters validationParameters)
{
if (validationParameters.TokenDecryptionKey != null)
yield return validationParameters.TokenDecryptionKey;
if (validationParameters.TokenDecryptionKeys != null)
foreach (SecurityKey key in validationParameters.TokenDecryptionKeys)
yield return key;
}
///
/// Creates a from a .
///
/// The to use as a source.
/// The value to set
/// Contains parameters for validating the token.
/// A containing the .
protected virtual ClaimsIdentity CreateClaimsIdentity(JwtSecurityToken jwtToken, string issuer, TokenValidationParameters validationParameters)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
var actualIssuer = issuer;
if (string.IsNullOrWhiteSpace(issuer))
{
LogHelper.LogVerbose(TokenLogMessages.IDX10244, ClaimsIdentity.DefaultIssuer);
actualIssuer = ClaimsIdentity.DefaultIssuer;
}
return MapInboundClaims ? CreateClaimsIdentityWithMapping(jwtToken, actualIssuer, validationParameters) : CreateClaimsIdentityWithoutMapping(jwtToken, actualIssuer, validationParameters);
}
private ClaimsIdentity CreateClaimsIdentityWithMapping(JwtSecurityToken jwtToken, string actualIssuer, TokenValidationParameters validationParameters)
{
ClaimsIdentity identity = validationParameters.CreateClaimsIdentity(jwtToken, actualIssuer);
foreach (Claim jwtClaim in jwtToken.Claims)
{
if (_inboundClaimFilter.Contains(jwtClaim.Type))
continue;
string claimType;
bool wasMapped = true;
if (!_inboundClaimTypeMap.TryGetValue(jwtClaim.Type, out claimType))
{
claimType = jwtClaim.Type;
wasMapped = false;
}
if (claimType == ClaimTypes.Actor)
{
if (identity.Actor != null)
throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX12710, JwtRegisteredClaimNames.Actort, jwtClaim.Value)));
if (CanReadToken(jwtClaim.Value))
{
JwtSecurityToken actor = ReadToken(jwtClaim.Value) as JwtSecurityToken;
identity.Actor = CreateClaimsIdentity(actor, actualIssuer, validationParameters);
}
}
Claim claim = new Claim(claimType, jwtClaim.Value, jwtClaim.ValueType, actualIssuer, actualIssuer, identity);
if (jwtClaim.Properties.Count > 0)
{
foreach (var kv in jwtClaim.Properties)
{
claim.Properties[kv.Key] = kv.Value;
}
}
if (wasMapped)
claim.Properties[ShortClaimTypeProperty] = jwtClaim.Type;
identity.AddClaim(claim);
}
return identity;
}
private ClaimsIdentity CreateClaimsIdentityWithoutMapping(JwtSecurityToken jwtToken, string actualIssuer, TokenValidationParameters validationParameters)
{
ClaimsIdentity identity = validationParameters.CreateClaimsIdentity(jwtToken, actualIssuer);
foreach (Claim jwtClaim in jwtToken.Claims)
{
if (_inboundClaimFilter.Contains(jwtClaim.Type))
continue;
string claimType = jwtClaim.Type;
if (claimType == ClaimTypes.Actor)
{
if (identity.Actor != null)
throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX12710, JwtRegisteredClaimNames.Actort, jwtClaim.Value)));
if (CanReadToken(jwtClaim.Value))
{
JwtSecurityToken actor = ReadToken(jwtClaim.Value) as JwtSecurityToken;
identity.Actor = CreateClaimsIdentity(actor, actualIssuer, validationParameters);
}
}
Claim claim = new Claim(claimType, jwtClaim.Value, jwtClaim.ValueType, actualIssuer, actualIssuer, identity);
if (jwtClaim.Properties.Count > 0)
{
foreach (var kv in jwtClaim.Properties)
claim.Properties[kv.Key] = kv.Value;
}
identity.AddClaim(claim);
}
return identity;
}
///
/// Creates the 'value' for the actor claim: { actort, 'value' }
///
/// as actor.
/// representing the actor.
/// If is not null:
/// If 'type' is 'string', return as string.
/// if 'type' is 'BootstrapContext' and 'BootstrapContext.SecurityToken' is 'JwtSecurityToken'
/// if 'JwtSecurityToken.RawData' != null, return RawData.
/// else return .
/// if 'BootstrapContext.Token' != null, return 'Token'.
/// default: new ( ( actor.Claims ).
///
/// 'actor' is null.
protected virtual string CreateActorValue(ClaimsIdentity actor)
{
if (actor == null)
throw LogHelper.LogArgumentNullException(nameof(actor));
if (actor.BootstrapContext != null)
{
string encodedJwt = actor.BootstrapContext as string;
if (encodedJwt != null)
{
LogHelper.LogVerbose(LogMessages.IDX12713);
return encodedJwt;
}
JwtSecurityToken jwtToken = actor.BootstrapContext as JwtSecurityToken;
if (jwtToken != null)
{
if (jwtToken.RawData != null)
{
LogHelper.LogVerbose(LogMessages.IDX12714);
return jwtToken.RawData;
}
else
{
LogHelper.LogVerbose(LogMessages.IDX12715);
return this.WriteToken(jwtToken);
}
}
LogHelper.LogVerbose(LogMessages.IDX12711);
}
LogHelper.LogVerbose(LogMessages.IDX12712);
return WriteToken(new JwtSecurityToken(claims: actor.Claims));
}
///
/// Determines if the audiences found in a are valid.
///
/// The audiences found in the .
/// The being validated.
/// required for validation.
/// See for additional details.
protected virtual void ValidateAudience(IEnumerable<string> audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
Validators.ValidateAudience(audiences, jwtToken, validationParameters);
}
///
/// Validates the lifetime of a .
///
/// The value of the 'nbf' claim if it exists in the 'jwtToken'.
/// The value of the 'exp' claim if it exists in the 'jwtToken'.
/// The being validated.
/// required for validation.
/// for additional details.
protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
Validators.ValidateLifetime(notBefore, expires, jwtToken, validationParameters);
}
///
/// Determines if the issuer found in a is valid.
///
/// The issuer to validate
/// The that is being validated.
/// required for validation.
/// The issuer to use when creating the (s) in the .
/// for additional details.
protected virtual string ValidateIssuer(string issuer, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
return Validators.ValidateIssuer(issuer, jwtToken, validationParameters);
}
///
/// Determines if a is already validated.
///
/// The value of the 'exp' claim if it exists in the '.
/// The that is being validated.
/// required for validation.
protected virtual void ValidateTokenReplay(DateTime? expires, string securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateTokenReplay(expires, securityToken, validationParameters);
}
///
/// Returns a to use when validating the signature of a token.
///
/// The representation of the token that is being validated.
/// The that is being validated.
/// A required for validation.
/// Returns a to use for signature validation.
/// If key fails to resolve, then null is returned
protected virtual SecurityKey ResolveIssuerSigningKey(string token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
return JwtTokenUtilities.ResolveTokenSigningKey(jwtToken.Header.Kid, jwtToken.Header.X5t, validationParameters);
}
///
/// Returns a to use when decryption a JWE.
///
/// The the token that is being decrypted.
/// The that is being decrypted.
/// A required for validation.
/// Returns a to use for signature validation.
/// If key fails to resolve, then null is returned
protected virtual SecurityKey ResolveTokenDecryptionKey(string token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
if (!string.IsNullOrEmpty(jwtToken.Header.Kid))
{
if (validationParameters.TokenDecryptionKey != null
&& string.Equals(validationParameters.TokenDecryptionKey.KeyId, jwtToken.Header.Kid, validationParameters.TokenDecryptionKey is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
return validationParameters.TokenDecryptionKey;
if (validationParameters.TokenDecryptionKeys != null)
{
foreach (var key in validationParameters.TokenDecryptionKeys)
{
if (key != null && string.Equals(key.KeyId, jwtToken.Header.Kid, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
return key;
}
}
}
if (!string.IsNullOrEmpty(jwtToken.Header.X5t))
{
if (validationParameters.TokenDecryptionKey != null)
{
if (string.Equals(validationParameters.TokenDecryptionKey.KeyId, jwtToken.Header.X5t, validationParameters.TokenDecryptionKey is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
return validationParameters.TokenDecryptionKey;
X509SecurityKey x509Key = validationParameters.TokenDecryptionKey as X509SecurityKey;
if (x509Key != null && string.Equals(x509Key.X5t, jwtToken.Header.X5t, StringComparison.OrdinalIgnoreCase))
return validationParameters.TokenDecryptionKey;
}
if (validationParameters.TokenDecryptionKeys != null)
{
foreach (var key in validationParameters.TokenDecryptionKeys)
{
if (key != null && string.Equals(key.KeyId, jwtToken.Header.X5t, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
return key;
X509SecurityKey x509Key = key as X509SecurityKey;
if (x509Key != null && string.Equals(x509Key.X5t, jwtToken.Header.X5t, StringComparison.OrdinalIgnoreCase))
return key;
}
}
}
return null;
}
///
/// Decrypts a JWE and returns the clear text
///
/// the JWE that contains the cypher text.
/// contains crypto material.
/// the decoded / cleartext contents of the JWE.
/// if 'jwtToken' is null.
/// if 'validationParameters' is null.
/// if 'jwtToken.Header.enc' is null or empty.
/// if 'jwtToken.Header.kid' is not null AND decryption fails.
/// if the JWE was not able to be decrypted.
protected string DecryptToken(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
if (string.IsNullOrEmpty(jwtToken.Header.Enc))
throw LogHelper.LogExceptionMessage(new SecurityTokenException(LogHelper.FormatInvariant(TokenLogMessages.IDX10612)));
var keys = GetContentEncryptionKeys(jwtToken, validationParameters);
return JwtTokenUtilities.DecryptJwtToken(jwtToken, validationParameters, new JwtTokenDecryptionParameters
{
Alg = jwtToken.Header.Alg,
AuthenticationTag = jwtToken.RawAuthenticationTag,
Ciphertext = jwtToken.RawCiphertext,
DecompressionFunction = JwtTokenUtilities.DecompressToken,
Enc = jwtToken.Header.Enc,
EncodedHeader = jwtToken.EncodedHeader,
EncodedToken = jwtToken.RawData,
InitializationVector= jwtToken.RawInitializationVector,
Keys = keys,
Zip = jwtToken.Header.Zip,
});
}
internal IEnumerable GetContentEncryptionKeys(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
IEnumerable keys = null;
if (validationParameters.TokenDecryptionKeyResolver != null)
keys = validationParameters.TokenDecryptionKeyResolver(jwtToken.RawData, jwtToken, jwtToken.Header.Kid, validationParameters);
else
{
var key = ResolveTokenDecryptionKey(jwtToken.RawData, jwtToken, validationParameters);
if (key != null)
keys = new List { key };
}
// control gets here if:
// 1. User specified delegate: TokenDecryptionKeyResolver returned null
// 2. ResolveTokenDecryptionKey returned null
// Try all the keys. This is the degenerate case, not concerned about perf.
if (keys == null)
keys = GetAllDecryptionKeys(validationParameters);
if (jwtToken.Header.Alg.Equals(JwtConstants.DirectKeyUseAlg, StringComparison.Ordinal))
return keys;
var unwrappedKeys = new List();
// keep track of exceptions thrown, keys that were tried
var exceptionStrings = new StringBuilder();
var keysAttempted = new StringBuilder();
foreach (var key in keys)
{
try
{
if (key.CryptoProviderFactory.IsSupportedAlgorithm(jwtToken.Header.Alg, key))
{
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(key, jwtToken.Header.Alg);
var unwrappedKey = kwp.UnwrapKey(Base64UrlEncoder.DecodeBytes(jwtToken.RawEncryptedKey));
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
}
}
catch (Exception ex)
{
exceptionStrings.AppendLine(ex.ToString());
}
keysAttempted.AppendLine(key.ToString());
}
if (unwrappedKeys.Count > 0 || exceptionStrings.Length == 0)
return unwrappedKeys;
else
throw LogHelper.LogExceptionMessage(new SecurityTokenKeyWrapException(LogHelper.FormatInvariant(TokenLogMessages.IDX10618, keysAttempted, exceptionStrings, jwtToken)));
}
private static byte[] GetSymmetricSecurityKey(SecurityKey key)
{
if (key == null)
throw LogHelper.LogArgumentNullException(nameof(key));
// try to use the provided key directly.
SymmetricSecurityKey symmetricSecurityKey = key as SymmetricSecurityKey;
if (symmetricSecurityKey != null)
return symmetricSecurityKey.Key;
else
{
JsonWebKey jsonWebKey = key as JsonWebKey;
if (jsonWebKey != null && jsonWebKey.K != null)
return Base64UrlEncoder.DecodeBytes(jsonWebKey.K);
}
return null;
}
///
/// Validates the is an expected value.
///
/// The that signed the .
/// The to validate.
/// The current .
/// If the is a then the X509Certificate2 will be validated using the CertificateValidator.
protected virtual void ValidateIssuerSecurityKey(SecurityKey key, JwtSecurityToken securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateIssuerSecurityKey(key, securityToken, validationParameters);
}
///
/// Serializes to XML a token of the type handled by this instance.
///
/// The XML writer.
/// A token of type .
public override void WriteToken(XmlWriter writer, SecurityToken token)
{
throw new NotImplementedException();
}
}
}