|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 | 4 | using System.Diagnostics.CodeAnalysis;
|
| 5 | +using System.Security.Claims; |
| 6 | +using System.Text.Encodings.Web; |
| 7 | +using Microsoft.AspNetCore.Authentication; |
5 | 8 | using Microsoft.AspNetCore.Authentication.Cookies;
|
6 | 9 | using Microsoft.AspNetCore.Http;
|
7 | 10 | using Microsoft.AspNetCore.Identity;
|
| 11 | +using Microsoft.AspNetCore.Routing; |
8 | 12 | using Microsoft.Extensions.DependencyInjection.Extensions;
|
| 13 | +using Microsoft.Extensions.Logging; |
9 | 14 | using Microsoft.Extensions.Options;
|
10 | 15 |
|
11 | 16 | namespace Microsoft.Extensions.DependencyInjection;
|
@@ -109,6 +114,47 @@ public static class IdentityServiceCollectionExtensions
|
109 | 114 | return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
|
110 | 115 | }
|
111 | 116 |
|
| 117 | + /// <summary> |
| 118 | + /// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/> |
| 119 | + /// and configures authentication to support identity bearer tokens and cookies. |
| 120 | + /// </summary> |
| 121 | + /// <param name="services">The <see cref="IServiceCollection"/>.</param> |
| 122 | + /// <returns>The <see cref="IdentityBuilder"/>.</returns> |
| 123 | + public static IdentityBuilder AddIdentityApiEndpoints<TUser>(this IServiceCollection services) |
| 124 | + where TUser : class, new() |
| 125 | + => services.AddIdentityApiEndpoints<TUser>(_ => { }); |
| 126 | + |
| 127 | + /// <summary> |
| 128 | + /// Adds a set of common identity services to the application to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/> |
| 129 | + /// and configures authentication to support identity bearer tokens and cookies. |
| 130 | + /// </summary> |
| 131 | + /// <param name="services">The <see cref="IServiceCollection"/>.</param> |
| 132 | + /// <param name="configure">Configures the <see cref="IdentityOptions"/>.</param> |
| 133 | + /// <returns>The <see cref="IdentityBuilder"/>.</returns> |
| 134 | + public static IdentityBuilder AddIdentityApiEndpoints<TUser>(this IServiceCollection services, Action<IdentityOptions> configure) |
| 135 | + where TUser : class, new() |
| 136 | + { |
| 137 | + ArgumentNullException.ThrowIfNull(services); |
| 138 | + ArgumentNullException.ThrowIfNull(configure); |
| 139 | + |
| 140 | + services |
| 141 | + .AddAuthentication(IdentityConstants.BearerAndApplicationScheme) |
| 142 | + .AddScheme<AuthenticationSchemeOptions, CompositeIdentityHandler>(IdentityConstants.BearerAndApplicationScheme, null, compositeOptions => |
| 143 | + { |
| 144 | + compositeOptions.ForwardDefault = IdentityConstants.BearerScheme; |
| 145 | + compositeOptions.ForwardAuthenticate = IdentityConstants.BearerAndApplicationScheme; |
| 146 | + }) |
| 147 | + .AddIdentityBearerToken<TUser>() |
| 148 | + .AddIdentityCookies(); |
| 149 | + |
| 150 | + return services.AddIdentityCore<TUser>(o => |
| 151 | + { |
| 152 | + o.Stores.MaxLengthForKeys = 128; |
| 153 | + configure(o); |
| 154 | + }) |
| 155 | + .AddApiEndpoints(); |
| 156 | + } |
| 157 | + |
112 | 158 | /// <summary>
|
113 | 159 | /// Configures the application cookie.
|
114 | 160 | /// </summary>
|
@@ -141,4 +187,32 @@ public void PostConfigure(string? name, SecurityStampValidatorOptions options)
|
141 | 187 | options.TimeProvider ??= TimeProvider;
|
142 | 188 | }
|
143 | 189 | }
|
| 190 | + |
| 191 | + private sealed class CompositeIdentityHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder) |
| 192 | + : SignInAuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder) |
| 193 | + { |
| 194 | + protected override async Task<AuthenticateResult> HandleAuthenticateAsync() |
| 195 | + { |
| 196 | + var bearerResult = await Context.AuthenticateAsync(IdentityConstants.BearerScheme); |
| 197 | + |
| 198 | + // Only try to authenticate with the application cookie if there is no bearer token. |
| 199 | + if (!bearerResult.None) |
| 200 | + { |
| 201 | + return bearerResult; |
| 202 | + } |
| 203 | + |
| 204 | + // Cookie auth will return AuthenticateResult.NoResult() like bearer auth just did if there is no cookie. |
| 205 | + return await Context.AuthenticateAsync(IdentityConstants.ApplicationScheme); |
| 206 | + } |
| 207 | + |
| 208 | + protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) |
| 209 | + { |
| 210 | + throw new NotImplementedException(); |
| 211 | + } |
| 212 | + |
| 213 | + protected override Task HandleSignOutAsync(AuthenticationProperties? properties) |
| 214 | + { |
| 215 | + throw new NotImplementedException(); |
| 216 | + } |
| 217 | + } |
144 | 218 | }
|
0 commit comments