Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

171 lines
7.9 KiB

/*
* 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.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Security.Infrastructure;
namespace OpenIddict.Client.Owin;
// See https://github.com/owin/owin/issues/7 for more information.
using AuthenticateDelegate = Func<
/* Authentication types: */ string[]?,
/* Callback: */ Action<
/* Identity: */ IIdentity?,
/* Authentication properties: */ IDictionary<string, string?>?,
/* Authentication description: */ IDictionary<string, object?>?,
/* State: */ object?>,
/* State: */ object?,
Task>;
/// <summary>
/// Provides the entry point necessary to register the OpenIddict client handler in an OWIN pipeline.
/// </summary>
/// <remarks>
/// Note: this middleware is intended to be used with dependency injection containers
/// that support middleware resolution, like Autofac. Since it depends on scoped services,
/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientOwinMiddleware : AuthenticationMiddleware<AuthenticationOptions>
{
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientOwinMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the pipeline, if applicable.</param>
/// <param name="provider">The service provider.</param>
public OpenIddictClientOwinMiddleware(
OwinMiddleware? next,
IServiceProvider provider)
: base(next, new InternalOptions())
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public override async Task Invoke(IOwinContext context)
{
ArgumentNullException.ThrowIfNull(context);
// Resolve the list of forwarded authentication types from the options.
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOwinOptions>>().CurrentValue;
// Retrieve the existing authentication delegate.
var function = context.Get<AuthenticateDelegate?>("security.Authenticate");
try
{
// Replace the security.Authenticate delegate responsible for listing authentication types and returning
// identities to handle the forwarded authentication types managed by the OpenIddict OWIN client host.
context.Set<AuthenticateDelegate>("security.Authenticate", async (types, callback, state) =>
{
// Note: a null array is typically used by OWIN to resolve all the configured authentication types.
// In this case, iterate all the forwarded authentication types and call the callback action for each type.
if (types is null)
{
foreach (var description in options.ForwardedAuthenticationTypes)
{
callback(null, null, description.Properties, state);
}
}
else if (types.Length is > 0)
{
foreach (var type in types)
{
// If the specified authentication types don't match a forwarded authentication type
// managed by the OpenIddict OWIN client host, don't invoke the callback and let the
// corresponding authentication middleware handle it if it matches a registered type.
if (string.IsNullOrEmpty(type) ||
string.Equals(type, OpenIddictClientOwinDefaults.AuthenticationType, StringComparison.Ordinal) ||
!TryGetForwardedAuthenticationType(options.ForwardedAuthenticationTypes, type, out AuthenticationDescription? description))
{
continue;
}
// Resolve the authentication result returned by the OpenIddict OWIN client host:
// if the returned identity was created by the specified provider, return the result
// and stop iterating (only a single identity is returned by the OWIN host).
//
// Note: exceptions MUST NOT be caught to ensure they are properly surfaced to the caller
// (e.g if AuthenticateAsync("[provider name]") is called from an unsupported endpoint).
if (await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType)
is { Identity: ClaimsIdentity identity } result &&
identity.FindFirst(Claims.Private.ProviderName)?.Value is string provider &&
string.Equals(provider, description.AuthenticationType, StringComparison.Ordinal))
{
callback(
new ClaimsIdentity(
identity, identity.Claims, description.AuthenticationType,
identity.NameClaimType, identity.RoleClaimType),
result.Properties.Dictionary, description.Properties, state);
break;
}
}
}
// Always invoke the original authentication delegate to allow the other
// authentication middleware to return the authentication types they
// support and the identities they were able to extract, if applicable.
if (function is not null)
{
await function(types, callback, state);
}
});
await base.Invoke(context);
}
finally
{
// Restore the original authentication delegate.
context.Set("security.Authenticate", function);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool TryGetForwardedAuthenticationType(IReadOnlyList<AuthenticationDescription> descriptions,
string type, [NotNullWhen(true)] out AuthenticationDescription? result)
{
for (var index = 0; index < descriptions.Count; index++)
{
var description = descriptions[index];
if (string.Equals(description.AuthenticationType, type, StringComparison.Ordinal))
{
result = description;
return true;
}
}
result = null;
return false;
}
}
/// <summary>
/// Creates and returns a new <see cref="OpenIddictClientOwinHandler"/> instance.
/// </summary>
/// <returns>A new instance of the <see cref="OpenIddictClientOwinHandler"/> class.</returns>
protected override AuthenticationHandler<AuthenticationOptions> CreateHandler()
=> new OpenIddictClientOwinHandler(_provider);
/// <summary>
/// Provides the options used by the <see cref="OpenIddictClientOwinMiddleware"/> class.
/// </summary>
private sealed class InternalOptions : AuthenticationOptions
{
/// <summary>
/// Creates a new instance of the <see cref="InternalOptions"/> class.
/// </summary>
public InternalOptions() : base(OpenIddictClientOwinDefaults.AuthenticationType)
=> AuthenticationMode = AuthenticationMode.Passive;
}
}