Browse Source

Make the userinfo and token endpoints configurable per authentication demand

pull/1422/head
Kévin Chalet 4 years ago
parent
commit
3de3d8bbcb
  1. 6
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 10
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  3. 81
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  4. 38
      src/OpenIddict.Client/OpenIddictClientService.cs
  5. 2
      src/OpenIddict.Validation/OpenIddictValidationService.cs

6
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1668,6 +1668,12 @@ To register the server services, use 'services.AddOpenIddict().AddClient()'.</va
<data name="ID4013" xml:space="preserve">
<value>The issuer should be a valid absolute URL at this point.</value>
</data>
<data name="ID4014" xml:space="preserve">
<value>The token endpoint should be a valid absolute URL at this point.</value>
</data>
<data name="ID4015" xml:space="preserve">
<value>The userinfo endpoint should be a valid absolute URL at this point.</value>
</data>
<data name="ID6000" xml:space="preserve">
<value>An error occurred while validating the token '{Token}'.</value>
</data>

10
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -294,6 +294,16 @@ public static partial class OpenIddictClientEvents
/// </summary>
public string? ResponseType { get; set; }
/// <summary>
/// Gets or sets the address of the token endpoint, if applicable.
/// </summary>
public Uri? TokenEndpoint { get; set; }
/// <summary>
/// Gets or sets the address of the userinfo endpoint, if applicable.
/// </summary>
public Uri? UserinfoEndpoint { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an authorization
/// code should be extracted from the current context.

81
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -52,6 +52,7 @@ public static partial class OpenIddictClientHandlers
EvaluateValidatedBackchannelTokens.Descriptor,
ResolveTokenEndpoint.Descriptor,
AttachTokenRequestParameters.Descriptor,
SendTokenRequest.Descriptor,
ValidateTokenErrorParameters.Descriptor,
@ -69,6 +70,7 @@ public static partial class OpenIddictClientHandlers
ValidateBackchannelAccessToken.Descriptor,
ValidateRefreshToken.Descriptor,
ResolveUserinfoEndpoint.Descriptor,
EvaluateValidatedUserinfoToken.Descriptor,
AttachUserinfoRequestParameters.Descriptor,
SendUserinfoRequest.Descriptor,
@ -1406,6 +1408,37 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for resolving the address of the token endpoint.
/// </summary>
public class ResolveTokenEndpoint : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ResolveTokenEndpoint>()
.SetOrder(EvaluateValidatedBackchannelTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context!!)
{
var configuration = await context.Registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.TokenEndpoint is not { IsAbsoluteUri: true } ||
!configuration.TokenEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
}
context.TokenEndpoint = configuration.TokenEndpoint;
}
}
/// <summary>
/// Contains the logic responsible for attaching the parameters to the token request, if applicable.
/// </summary>
@ -1417,7 +1450,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<AttachTokenRequestParameters>()
.SetOrder(EvaluateValidatedBackchannelTokens.Descriptor.Order + 1_000)
.SetOrder(ResolveTokenEndpoint.Descriptor.Order + 1_000)
.Build();
/// <inheritdoc/>
@ -1429,7 +1462,7 @@ public static partial class OpenIddictClientHandlers
{
return default;
}
// Attach a new request instance if necessary.
context.TokenRequest ??= new OpenIddictRequest();
@ -1501,9 +1534,12 @@ public static partial class OpenIddictClientHandlers
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context!!)
{
Debug.Assert(context.TokenEndpoint is { IsAbsoluteUri: true } endpoint &&
endpoint.IsWellFormedOriginalString(), SR.GetResourceString(SR.ID4014));
Debug.Assert(context.TokenRequest is not null, SR.GetResourceString(SR.ID4008));
context.TokenResponse = await _service.SendTokenRequestAsync(context.Registration, context.TokenRequest);
context.TokenResponse = await _service.SendTokenRequestAsync(
context.Registration, context.TokenEndpoint, context.TokenRequest);
}
}
@ -2179,6 +2215,37 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for resolving the address of the userinfo endpoint.
/// </summary>
public class ResolveUserinfoEndpoint : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ResolveUserinfoEndpoint>()
.SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context!!)
{
var configuration = await context.Registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.UserinfoEndpoint is not { IsAbsoluteUri: true } ||
!configuration.UserinfoEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.UserinfoEndpoint));
}
context.UserinfoEndpoint = configuration.UserinfoEndpoint;
}
}
/// <summary>
/// Contains the logic responsible for determining whether a userinfo token should be validated.
/// </summary>
@ -2190,7 +2257,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<EvaluateValidatedUserinfoToken>()
.SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000)
.SetOrder(ResolveUserinfoEndpoint.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -2219,7 +2286,7 @@ public static partial class OpenIddictClientHandlers
// or responses will be extracted and validated if the userinfo endpoint and either
// a frontchannel or backchannel access token was extracted and is available.
GrantTypes.AuthorizationCode or GrantTypes.Implicit or GrantTypes.RefreshToken
when configuration.UserinfoEndpoint is not null && context switch
when context.UserinfoEndpoint is not null && context switch
{
{ ExtractBackchannelAccessToken: true, BackchannelAccessToken.Length: > 0 } => true,
{ ExtractFrontchannelAccessToken: true, FrontchannelAccessToken.Length: > 0 } => true,
@ -2293,6 +2360,8 @@ public static partial class OpenIddictClientHandlers
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context!!)
{
Debug.Assert(context.UserinfoEndpoint is { IsAbsoluteUri: true } endpoint &&
endpoint.IsWellFormedOriginalString(), SR.GetResourceString(SR.ID4015));
Debug.Assert(context.UserinfoRequest is not null, SR.GetResourceString(SR.ID4008));
// Note: userinfo responses can be of two types:
@ -2300,7 +2369,7 @@ public static partial class OpenIddictClientHandlers
// - application/jwt responses containing a signed/encrypted JSON Web Token containing the user claims.
(context.UserinfoResponse, (context.UserinfoTokenPrincipal, context.UserinfoToken)) =
await _service.SendUserinfoRequestAsync(context.Registration, context.UserinfoRequest);
await _service.SendUserinfoRequestAsync(context.Registration, context.UserinfoEndpoint, context.UserinfoRequest);
}
}

38
src/OpenIddict.Client/OpenIddictClientService.cs

@ -413,19 +413,16 @@ public class OpenIddictClientService
/// Sends the token request and retrieves the corresponding response.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <param name="address">The address of the token endpoint.</param>
/// <param name="request">The token request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The token response.</returns>
public async ValueTask<OpenIddictResponse> SendTokenRequestAsync(
OpenIddictClientRegistration registration!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
OpenIddictClientRegistration registration!!, Uri address!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
{
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.TokenEndpoint is not { IsAbsoluteUri: true } ||
!configuration.TokenEndpoint.IsWellFormedOriginalString())
if (!address.IsAbsoluteUri || !address.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
}
cancellationToken.ThrowIfCancellationRequested();
@ -454,7 +451,7 @@ public class OpenIddictClientService
{
var context = new PrepareTokenRequestContext(transaction)
{
Address = configuration.TokenEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -476,7 +473,7 @@ public class OpenIddictClientService
{
var context = new ApplyTokenRequestContext(transaction)
{
Address = configuration.TokenEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -498,7 +495,7 @@ public class OpenIddictClientService
{
var context = new ExtractTokenResponseContext(transaction)
{
Address = configuration.TokenEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -522,7 +519,7 @@ public class OpenIddictClientService
{
var context = new HandleTokenResponseContext(transaction)
{
Address = configuration.TokenEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request,
@ -560,19 +557,16 @@ public class OpenIddictClientService
/// Sends the userinfo request and retrieves the corresponding response.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <param name="address">The address of the userinfo endpoint.</param>
/// <param name="request">The userinfo request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The response and the principal extracted from the userinfo response or the userinfo token.</returns>
public async ValueTask<(OpenIddictResponse Response, (ClaimsPrincipal? Principal, string? Token))> SendUserinfoRequestAsync(
OpenIddictClientRegistration registration!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
OpenIddictClientRegistration registration!!, Uri address!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
{
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.UserinfoEndpoint is not { IsAbsoluteUri: true } ||
!configuration.UserinfoEndpoint.IsWellFormedOriginalString())
if (!address.IsAbsoluteUri || !address.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.UserinfoEndpoint));
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
}
cancellationToken.ThrowIfCancellationRequested();
@ -601,7 +595,7 @@ public class OpenIddictClientService
{
var context = new PrepareUserinfoRequestContext(transaction)
{
Address = configuration.UserinfoEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -623,7 +617,7 @@ public class OpenIddictClientService
{
var context = new ApplyUserinfoRequestContext(transaction)
{
Address = configuration.UserinfoEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -645,7 +639,7 @@ public class OpenIddictClientService
{
var context = new ExtractUserinfoResponseContext(transaction)
{
Address = configuration.UserinfoEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -669,7 +663,7 @@ public class OpenIddictClientService
{
var context = new HandleUserinfoResponseContext(transaction)
{
Address = configuration.UserinfoEndpoint,
Address = address,
Issuer = registration.Issuer,
Registration = registration,
Request = request,

2
src/OpenIddict.Validation/OpenIddictValidationService.cs

@ -311,7 +311,7 @@ public class OpenIddictValidationService
public async ValueTask<ClaimsPrincipal> IntrospectTokenAsync(
Uri address!!, string token, string? hint, CancellationToken cancellationToken = default)
{
if (!address.IsAbsoluteUri)
if (!address.IsAbsoluteUri || !address.IsWellFormedOriginalString())
{
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
}

Loading…
Cancel
Save