Browse Source

Decouple the token validation/generation event handlers from the request processing logic

pull/1309/head
Kévin Chalet 5 years ago
parent
commit
81273ec362
  1. 35
      src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
  2. 57
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  3. 354
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  4. 33
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  5. 19
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  6. 4
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  7. 8
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

35
src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs

@ -29,14 +29,32 @@ namespace OpenIddict.Server
} }
/// <summary> /// <summary>
/// Gets or sets the request. /// Gets or sets the request, or <c>null</c> if it is not available.
/// </summary> /// </summary>
public OpenIddictRequest Request public OpenIddictRequest? Request
{ {
get => Transaction.Request!; get => Transaction.Request;
set => Transaction.Request = value; set => Transaction.Request = value;
} }
/// <summary>
/// Gets or sets the client identifier of the application
/// the resulting token will be issued to, if applicable.
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a token entry
/// should be created to persist token metadata in a database.
/// </summary>
public bool CreateTokenEntry { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database.
/// </summary>
public bool PersistTokenPayload { get; set; }
/// <summary> /// <summary>
/// Gets or sets the security principal used to create the token. /// Gets or sets the security principal used to create the token.
/// </summary> /// </summary>
@ -82,14 +100,19 @@ namespace OpenIddict.Server
} }
/// <summary> /// <summary>
/// Gets or sets the request. /// Gets or sets the request, or <c>null</c> if it is not available.
/// </summary> /// </summary>
public OpenIddictRequest Request public OpenIddictRequest? Request
{ {
get => Transaction.Request!; get => Transaction.Request;
set => Transaction.Request = value; set => Transaction.Request = value;
} }
/// <summary>
/// Gets or sets a boolean indicating whether lifetime validation is disabled.
/// </summary>
public bool DisableLifetimeValidation { get; set; }
/// <summary> /// <summary>
/// Gets or sets the security token handler used to validate the token. /// Gets or sets the security token handler used to validate the token.
/// </summary> /// </summary>

57
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -72,7 +72,12 @@ namespace OpenIddict.Server
/* /*
* Token request handling: * Token request handling:
*/ */
AttachPrincipal.Descriptor); AttachPrincipal.Descriptor,
/*
* Token response handling:
*/
NormalizeErrorResponse.Descriptor);
/// <summary> /// <summary>
/// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers. /// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers.
@ -1716,6 +1721,56 @@ namespace OpenIddict.Server
return default; return default;
} }
} }
/// <summary>
/// Contains the logic responsible of converting token errors to standard invalid_grant responses.
/// </summary>
public class NormalizeErrorResponse : IOpenIddictServerHandler<ApplyTokenResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyTokenResponseContext>()
.UseSingletonHandler<NormalizeErrorResponse>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ApplyTokenResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(context.Error))
{
return default;
}
// If the error indicates an invalid token caused by an invalid authorization,
// device code or refresh token, return a standard invalid_grant.
if (context.Request is null || !(context.Request.IsAuthorizationCodeGrantType() ||
context.Request.IsDeviceCodeGrantType() ||
context.Request.IsRefreshTokenGrantType()))
{
return default;
}
context.Response.Error = context.Error switch
{
Errors.InvalidToken or Errors.ExpiredToken => Errors.InvalidGrant,
_ => context.Error // Otherwise, keep the error as-is.
};
return default;
}
}
} }
} }
} }

354
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -184,29 +184,25 @@ namespace OpenIddict.Server
!await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray())) !await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()))
{ {
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
description: context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Token => Errors.InvalidGrant, 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
_ => Errors.InvalidToken
},
description: context.EndpointType switch
{
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> SR.GetResourceString(SR.ID2001), => SR.GetResourceString(SR.ID2001),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.GetResourceString(SR.ID2002), => SR.GetResourceString(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.GetResourceString(SR.ID2003), => SR.GetResourceString(SR.ID2003),
_ => SR.GetResourceString(SR.ID2004) _ => SR.GetResourceString(SR.ID2004)
}, },
uri: context.EndpointType switch uri: context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
=> SR.FormatID8000(SR.ID2001), => SR.FormatID8000(SR.ID2001),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.FormatID8000(SR.ID2002), => SR.FormatID8000(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.FormatID8000(SR.ID2003), => SR.FormatID8000(SR.ID2003),
_ => SR.FormatID8000(SR.ID2004), _ => SR.FormatID8000(SR.ID2004),
@ -305,25 +301,22 @@ namespace OpenIddict.Server
context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token);
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
{
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
_ => Errors.InvalidToken
},
description: result.Exception switch description: result.Exception switch
{ {
SecurityTokenInvalidTypeException => context.EndpointType switch SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
=> SR.GetResourceString(SR.ID2005), => SR.GetResourceString(SR.ID2005),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.GetResourceString(SR.ID2006), => SR.GetResourceString(SR.ID2006),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.GetResourceString(SR.ID2007), => SR.GetResourceString(SR.ID2007),
OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2008), 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken)
=> SR.GetResourceString(SR.ID2008),
_ => SR.GetResourceString(SR.ID2089) _ => SR.GetResourceString(SR.ID2089)
}, },
@ -336,18 +329,19 @@ namespace OpenIddict.Server
}, },
uri: result.Exception switch uri: result.Exception switch
{ {
SecurityTokenInvalidTypeException => context.EndpointType switch SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
=> SR.FormatID8000(SR.ID2005), => SR.FormatID8000(SR.ID2005),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.FormatID8000(SR.ID2006), => SR.FormatID8000(SR.ID2006),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.FormatID8000(SR.ID2007), => SR.FormatID8000(SR.ID2007),
OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2008), 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken)
=> SR.FormatID8000(SR.ID2008),
_ => SR.FormatID8000(SR.ID2089) _ => SR.FormatID8000(SR.ID2089)
}, },
@ -629,36 +623,30 @@ namespace OpenIddict.Server
if (context.Principal is null) if (context.Principal is null)
{ {
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
description: context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Token => Errors.InvalidGrant, 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
_ => Errors.InvalidToken
},
description: context.EndpointType switch
{
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout
=> SR.GetResourceString(SR.ID2009),
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> SR.GetResourceString(SR.ID2001), => SR.GetResourceString(SR.ID2001),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.GetResourceString(SR.ID2002), => SR.GetResourceString(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.GetResourceString(SR.ID2003), => SR.GetResourceString(SR.ID2003),
1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken)
=> SR.GetResourceString(SR.ID2009),
_ => SR.GetResourceString(SR.ID2004) _ => SR.GetResourceString(SR.ID2004)
}, },
uri: context.EndpointType switch uri: context.ValidTokenTypes.Count switch
{ {
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
=> SR.FormatID8000(SR.ID2009),
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> SR.FormatID8000(SR.ID2001), => SR.FormatID8000(SR.ID2001),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode)
=> SR.FormatID8000(SR.ID2002), => SR.FormatID8000(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken)
=> SR.FormatID8000(SR.ID2003), => SR.FormatID8000(SR.ID2003),
1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken)
=> SR.FormatID8000(SR.ID2009),
_ => SR.FormatID8000(SR.ID2004) _ => SR.FormatID8000(SR.ID2004)
}); });
@ -735,30 +723,20 @@ namespace OpenIddict.Server
if (token is null) if (token is null)
{ {
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
description: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token => Errors.InvalidGrant, TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001),
_ => Errors.InvalidToken TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002),
}, TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003),
description: context.EndpointType switch
{
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> SR.GetResourceString(SR.ID2001),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
=> SR.GetResourceString(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.GetResourceString(SR.ID2003),
_ => SR.GetResourceString(SR.ID2004) _ => SR.GetResourceString(SR.ID2004)
}, },
uri: context.EndpointType switch uri: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001),
=> SR.FormatID8000(SR.ID2001), TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003),
=> SR.FormatID8000(SR.ID2002),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.FormatID8000(SR.ID2003),
_ => SR.FormatID8000(SR.ID2004) _ => SR.FormatID8000(SR.ID2004)
}); });
@ -766,88 +744,69 @@ namespace OpenIddict.Server
return; return;
} }
if (context.EndpointType == OpenIddictServerEndpointType.Token && (context.Request.IsAuthorizationCodeGrantType() || // If the token is already marked as redeemed, this may indicate that it was compromised.
context.Request.IsDeviceCodeGrantType() || // In this case, revoke the entire chain of tokens associated with the authorization.
context.Request.IsRefreshTokenGrantType())) // Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway.
// Note: the authorization itself is not revoked to allow the legitimate client to start a new flow.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed))
{ {
// If the authorization code/device code/refresh token is already marked as redeemed, this may indicate if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token))
// that it was compromised. In this case, revoke the entire chain of tokens associated with the authorization.
// Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway.
// Note: the authorization itself is not revoked to allow the legitimate client to start a new flow.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed))
{ {
if (!context.Request.IsRefreshTokenGrantType() || !await IsReusableAsync(token)) context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier);
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); context.Reject(
error: Errors.InvalidToken,
context.Reject( description: context.Principal.GetTokenType() switch
error: context.EndpointType switch {
{ TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2010),
OpenIddictServerEndpointType.Token => Errors.InvalidGrant, TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2011),
TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2012),
_ => Errors.InvalidToken
}, _ => SR.GetResourceString(SR.ID2013)
description: context.EndpointType switch },
{ uri: context.Principal.GetTokenType() switch
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() {
=> SR.GetResourceString(SR.ID2010), TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2010),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2011),
=> SR.GetResourceString(SR.ID2011), TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2012),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.GetResourceString(SR.ID2012), _ => SR.FormatID8000(SR.ID2013)
});
_ => SR.GetResourceString(SR.ID2013)
}, // Revoke all the token entries associated with the authorization.
uri: context.EndpointType switch await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token));
{
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> SR.FormatID8000(SR.ID2010),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
=> SR.FormatID8000(SR.ID2011),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.FormatID8000(SR.ID2012),
_ => SR.FormatID8000(SR.ID2013)
});
// Revoke all the token entries associated with the authorization.
await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token));
return;
}
return; return;
} }
if (context.Request.IsDeviceCodeGrantType()) return;
{ }
// If the device code is not marked as valid yet, return an authorization_pending error.
if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier);
context.Reject( // If the token is not marked as valid yet, return an authorization_pending error.
error: Errors.AuthorizationPending, if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive))
description: SR.GetResourceString(SR.ID2014), {
uri: SR.FormatID8000(SR.ID2014)); context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier);
return; context.Reject(
} error: Errors.AuthorizationPending,
description: SR.GetResourceString(SR.ID2014),
uri: SR.FormatID8000(SR.ID2014));
// If the device code is marked as rejected, return an access_denied error. return;
if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) }
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier);
context.Reject( // If the token is marked as rejected, return an access_denied error.
error: Errors.AccessDenied, if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected))
description: SR.GetResourceString(SR.ID2015), {
uri: SR.FormatID8000(SR.ID2015)); context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier);
return; context.Reject(
} error: Errors.AccessDenied,
} description: SR.GetResourceString(SR.ID2015),
uri: SR.FormatID8000(SR.ID2015));
return;
} }
if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid))
@ -855,30 +814,20 @@ namespace OpenIddict.Server
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier);
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
{ description: context.Principal.GetTokenType() switch
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
_ => Errors.InvalidToken
},
description: context.EndpointType switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016),
=> SR.GetResourceString(SR.ID2016), TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018),
=> SR.GetResourceString(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.GetResourceString(SR.ID2018),
_ => SR.GetResourceString(SR.ID2019) _ => SR.GetResourceString(SR.ID2019)
}, },
uri: context.EndpointType switch uri: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016),
=> SR.FormatID8000(SR.ID2016), TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018),
=> SR.FormatID8000(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.FormatID8000(SR.ID2018),
_ => SR.FormatID8000(SR.ID2019) _ => SR.FormatID8000(SR.ID2019)
}); });
@ -975,30 +924,20 @@ namespace OpenIddict.Server
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier);
context.Reject( context.Reject(
error: context.EndpointType switch error: Errors.InvalidToken,
{ description: context.Principal.GetTokenType() switch
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
_ => Errors.InvalidToken
},
description: context.EndpointType switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2020),
=> SR.GetResourceString(SR.ID2020), TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2021),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2022),
=> SR.GetResourceString(SR.ID2021),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.GetResourceString(SR.ID2022),
_ => SR.GetResourceString(SR.ID2023) _ => SR.GetResourceString(SR.ID2023)
}, },
uri: context.EndpointType switch uri: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2020),
=> SR.FormatID8000(SR.ID2020), TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2021),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2022),
=> SR.FormatID8000(SR.ID2021),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.FormatID8000(SR.ID2022),
_ => SR.FormatID8000(SR.ID2023) _ => SR.FormatID8000(SR.ID2023)
}); });
@ -1033,9 +972,7 @@ namespace OpenIddict.Server
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// Don't validate the lifetime of id_tokens used as id_token_hints. if (context.DisableLifetimeValidation)
if (context.ValidTokenTypes.Count is 1 && context.ValidTokenTypes.ElementAt(0) is TokenTypeHints.IdToken &&
context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout)
{ {
return default; return default;
} }
@ -1044,34 +981,24 @@ namespace OpenIddict.Server
if (date.HasValue && date.Value < DateTimeOffset.UtcNow) if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
{ {
context.Reject( context.Reject(
error: context.EndpointType switch error: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.DeviceCode => Errors.ExpiredToken,
=> Errors.ExpiredToken, _ => Errors.InvalidToken
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
_ => Errors.InvalidToken
}, },
description: context.EndpointType switch description: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016),
=> SR.GetResourceString(SR.ID2016), TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018),
=> SR.GetResourceString(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.GetResourceString(SR.ID2018),
_ => SR.GetResourceString(SR.ID2019) _ => SR.GetResourceString(SR.ID2019)
}, },
uri: context.EndpointType switch uri: context.Principal.GetTokenType() switch
{ {
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016),
=> SR.FormatID8000(SR.ID2016), TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018),
=> SR.FormatID8000(SR.ID2017),
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> SR.FormatID8000(SR.ID2018),
_ => SR.FormatID8000(SR.ID2019) _ => SR.FormatID8000(SR.ID2019)
}); });
@ -1170,6 +1097,11 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (!context.CreateTokenEntry)
{
return;
}
var descriptor = new OpenIddictTokenDescriptor var descriptor = new OpenIddictTokenDescriptor
{ {
AuthorizationId = context.Principal.GetAuthorizationId(), AuthorizationId = context.Principal.GetAuthorizationId(),
@ -1201,9 +1133,9 @@ namespace OpenIddict.Server
}; };
// If the client application is known, associate it with the token. // If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(context.Request.ClientId)) if (!string.IsNullOrEmpty(context.ClientId))
{ {
var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null) if (application is null)
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
@ -1386,25 +1318,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (!(context.TokenType switch if (!context.PersistTokenPayload)
{
// Access and refresh tokens can be converted to reference tokens
// if the corresponding option was enabled in the server options.
TokenTypeHints.AccessToken => context.Options.UseReferenceAccessTokens,
TokenTypeHints.RefreshToken => context.Options.UseReferenceRefreshTokens,
// By default, authorization/user codes are always converted to reference tokens.
TokenTypeHints.AuthorizationCode or TokenTypeHints.UserCode => true,
// Device codes are only converted to reference tokens if they are not generated
// as part of a device code swap made by the user code verification endpoint.
TokenTypeHints.DeviceCode => context.EndpointType is not OpenIddictServerEndpointType.Verification,
// Identity tokens cannot be converted to reference tokens.
TokenTypeHints.IdToken => false,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
}))
{ {
return; return;
} }

33
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -695,6 +695,9 @@ namespace OpenIddict.Server
var notification = new ValidateTokenContext(context.Transaction) var notification = new ValidateTokenContext(context.Transaction)
{ {
// Don't validate the lifetime of id_tokens used as id_token_hints.
DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or
OpenIddictServerEndpointType.Logout,
Token = context.IdentityToken, Token = context.IdentityToken,
ValidTokenTypes = { TokenTypeHints.IdToken } ValidTokenTypes = { TokenTypeHints.IdToken }
}; };
@ -2218,6 +2221,11 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Access tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options.
PersistTokenPayload = context.Options.UseReferenceAccessTokens,
Principal = context.AccessTokenPrincipal!, Principal = context.AccessTokenPrincipal!,
TokenType = TokenTypeHints.AccessToken TokenType = TokenTypeHints.AccessToken
}; };
@ -2280,6 +2288,9 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.AuthorizationCodePrincipal!, Principal = context.AuthorizationCodePrincipal!,
TokenType = TokenTypeHints.AuthorizationCode TokenType = TokenTypeHints.AuthorizationCode
}; };
@ -2342,6 +2353,16 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Device codes can be converted to reference tokens if they are not generated
// as part of a device code swap made by the user code verification endpoint.
PersistTokenPayload = context.EndpointType switch
{
OpenIddictServerEndpointType.Verification => false,
_ => !context.Options.DisableTokenStorage
},
Principal = context.DeviceCodePrincipal!, Principal = context.DeviceCodePrincipal!,
TokenType = TokenTypeHints.DeviceCode TokenType = TokenTypeHints.DeviceCode
}; };
@ -2404,6 +2425,11 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Refresh tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options.
PersistTokenPayload = context.Options.UseReferenceRefreshTokens,
Principal = context.RefreshTokenPrincipal!, Principal = context.RefreshTokenPrincipal!,
TokenType = TokenTypeHints.RefreshToken TokenType = TokenTypeHints.RefreshToken
}; };
@ -2711,6 +2737,9 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.UserCodePrincipal!, Principal = context.UserCodePrincipal!,
TokenType = TokenTypeHints.UserCode TokenType = TokenTypeHints.UserCode
}; };
@ -2773,6 +2802,10 @@ namespace OpenIddict.Server
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Identity tokens cannot never be reference tokens.
PersistTokenPayload = false,
Principal = context.IdentityTokenPrincipal!, Principal = context.IdentityTokenPrincipal!,
TokenType = TokenTypeHints.IdToken TokenType = TokenTypeHints.IdToken
}; };

19
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

@ -3436,6 +3436,9 @@ namespace OpenIddict.Server.IntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3535,6 +3538,9 @@ namespace OpenIddict.Server.IntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3618,6 +3624,9 @@ namespace OpenIddict.Server.IntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3703,6 +3712,9 @@ namespace OpenIddict.Server.IntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3775,18 +3787,13 @@ namespace OpenIddict.Server.IntegrationTests
builder.UseInlineHandler(context => builder.UseInlineHandler(context =>
{ {
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(context.Request.IsAuthorizationCodeGrantType() ? .SetTokenType(context.Request!.IsAuthorizationCodeGrantType() ?
TokenTypeHints.AuthorizationCode : TokenTypeHints.AuthorizationCode :
TokenTypeHints.RefreshToken) TokenTypeHints.RefreshToken)
.SetPresenters("Fabrikam") .SetPresenters("Fabrikam")
.SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC") .SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC")
.SetClaim(Claims.Subject, "Bob le Bricoleur"); .SetClaim(Claims.Subject, "Bob le Bricoleur");
if (context.Request.IsAuthorizationCodeGrantType())
{
context.Principal.SetPresenters("Fabrikam");
}
return default; return default;
}); });

4
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs

@ -221,8 +221,8 @@ namespace OpenIddict.Server.IntegrationTests
// Assert // Assert
Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri);
} }
[Theory] [Theory]

8
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

@ -705,8 +705,8 @@ namespace OpenIddict.Server.IntegrationTests
// Assert // Assert
Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny<OpenIddictToken>(), It.IsAny<CancellationToken>()), Times.Never()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny<OpenIddictToken>(), It.IsAny<CancellationToken>()), Times.Never());
@ -766,8 +766,8 @@ namespace OpenIddict.Server.IntegrationTests
// Assert // Assert
Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny<OpenIddictToken>(), It.IsAny<CancellationToken>()), Times.Never()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny<OpenIddictToken>(), It.IsAny<CancellationToken>()), Times.Never());

Loading…
Cancel
Save