Browse Source

Add Microsoft Account support

pull/1464/head
Kévin Chalet 4 years ago
parent
commit
2e968f36a6
  1. 39
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
  2. 68
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
  3. 13
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  4. 1
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  5. 4
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
  6. 17
      src/OpenIddict.Client/OpenIddictClientHandlers.cs

39
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs

@ -18,8 +18,47 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/* /*
* Configuration response handling: * Configuration response handling:
*/ */
AmendIssuer.Descriptor,
AmendClientAuthenticationMethods.Descriptor); AmendClientAuthenticationMethods.Descriptor);
/// <summary>
/// Contains the logic responsible for amending the issuer for the providers that require it.
/// </summary>
public class AmendIssuer : IOpenIddictClientHandler<HandleConfigurationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<HandleConfigurationResponseContext>()
.UseSingletonHandler<AmendIssuer>()
.SetOrder(ValidateIssuer.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Note: the server configuration metadata returned by the Microsoft Account "common" tenant
// uses "https://login.microsoftonline.com/{tenantid}/v2.0" as the issuer to indicate that
// the issued identity tokens will have a dynamic issuer claim whose value will be resolved
// based on the client identity. As required by RFC8414, OpenIddict would automatically reject
// such responses as the issuer wouldn't match the expected value. To work around that, the issuer
// is replaced by this handler to always use "https://login.microsoftonline.com/common/v2.0".
if (context.Registration.GetProviderName() is Providers.Microsoft)
{
context.Response[Metadata.Issuer] = "https://login.microsoftonline.com/common/v2.0";
}
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for amending the client /// Contains the logic responsible for amending the client
/// authentication methods for the providers that require it. /// authentication methods for the providers that require it.

68
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs

@ -0,0 +1,68 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using static OpenIddict.Client.OpenIddictClientHandlers.Protection;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Client.WebIntegration;
public static partial class OpenIddictClientWebIntegrationHandlers
{
public static class Protection
{
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token validation:
*/
AmendTokenValidationParameters.Descriptor);
/// <summary>
/// Contains the logic responsible for amending the token validation parameters for the providers that require it.
/// </summary>
public class AmendTokenValidationParameters : IOpenIddictClientHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<AmendTokenValidationParameters>()
.SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Note: the client registration may be null (e.g when validating a state token).
// In this case, don't amend the default token validation parameters.
if (context.Registration is null)
{
return default;
}
context.TokenValidationParameters.ValidateIssuer = context.Registration.GetProviderName() switch
{
// While the Microsoft Account provider uses the "common" tenant, the issued tokens include
// a dynamic issuer claim corresponding to the tenant instance that is associated with
// the client application. Since the tenant cannot be inferred when targeting the common
// tenant, issuer validation is manually disabled for the Microsoft Account provider.
Providers.Microsoft => false,
_ => context.TokenValidationParameters.ValidateIssuer
};
return default;
}
}
}
}

13
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs

@ -101,10 +101,17 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// logic from mapping the parameters to CLR claims. To work around that, this handler // logic from mapping the parameters to CLR claims. To work around that, this handler
// is responsible for extracting the nested payload and replacing the userinfo response. // is responsible for extracting the nested payload and replacing the userinfo response.
if (context.Registration.GetProviderName() is Providers.Twitter) var parameter = context.Registration.GetProviderName() switch
{
Providers.Twitter => "data",
_ => null
};
if (!string.IsNullOrEmpty(parameter))
{ {
context.Response = new OpenIddictResponse(context.Response["data"]?.GetNamedParameters() ?? context.Response = new OpenIddictResponse(context.Response[parameter]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334("data"))); throw new InvalidOperationException(SR.FormatID0334(parameter)));
} }
return default; return default;

1
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

@ -31,6 +31,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
FormatNonStandardScopeParameter.Descriptor) FormatNonStandardScopeParameter.Descriptor)
.AddRange(Discovery.DefaultHandlers) .AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers) .AddRange(Exchange.DefaultHandlers)
.AddRange(Protection.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers); .AddRange(Userinfo.DefaultHandlers);
/// <summary> /// <summary>

4
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

@ -25,6 +25,10 @@
<Environment Issuer="https://accounts.google.com/" /> <Environment Issuer="https://accounts.google.com/" />
</Provider> </Provider>
<Provider Name="Microsoft" Documentation="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc">
<Environment Issuer="https://login.microsoftonline.com/common/v2.0" />
</Provider>
<Provider Name="Reddit" Documentation="https://github.com/reddit-archive/reddit/wiki/OAuth2"> <Provider Name="Reddit" Documentation="https://github.com/reddit-archive/reddit/wiki/OAuth2">
<Environment Issuer="https://www.reddit.com/"> <Environment Issuer="https://www.reddit.com/">
<Configuration AuthorizationEndpoint="https://www.reddit.com/api/v1/authorize" <Configuration AuthorizationEndpoint="https://www.reddit.com/api/v1/authorize"

17
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -1210,6 +1210,8 @@ public static partial class OpenIddictClientHandlers
// ensure the at_hash claim matches the hash of the actual access token. // ensure the at_hash claim matches the hash of the actual access token.
if (!string.IsNullOrEmpty(context.FrontchannelAccessToken)) if (!string.IsNullOrEmpty(context.FrontchannelAccessToken))
{ {
// Note: the at_hash MUST be present in identity tokens returned from the authorization endpoint.
// See https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 for more information.
var hash = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.AccessTokenHash); var hash = context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.AccessTokenHash);
if (string.IsNullOrEmpty(hash)) if (string.IsNullOrEmpty(hash))
{ {
@ -2470,18 +2472,11 @@ public static partial class OpenIddictClientHandlers
using var algorithm = GetHashAlgorithm(name) ?? using var algorithm = GetHashAlgorithm(name) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0295)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0295));
// Note: the at_hash is optional for backchannel identity tokens returned from the token endpoint.
// As such, the validation routine is only enforced if the at_hash claim is present in the token.
// See https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 for more information.
var hash = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.AccessTokenHash); var hash = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.AccessTokenHash);
if (string.IsNullOrEmpty(hash)) if (!string.IsNullOrEmpty(hash) && !ValidateTokenHash(algorithm, context.BackchannelAccessToken, hash))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2126(Claims.AccessTokenHash),
uri: SR.FormatID8000(SR.ID2126));
return default;
}
if (!ValidateTokenHash(algorithm, context.BackchannelAccessToken, hash))
{ {
context.Reject( context.Reject(
error: Errors.InvalidRequest, error: Errors.InvalidRequest,

Loading…
Cancel
Save