Skip to content

Commit 364c640

Browse files
committed
Add MapIdentity<TUser>()
1 parent 914acff commit 364c640

28 files changed

+868
-67
lines changed

AspNetCore.sln

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
17801780
EndProject
17811781
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorUnitedApp", "src\Components\Samples\BlazorUnitedApp\BlazorUnitedApp.csproj", "{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}"
17821782
EndProject
1783+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Endpoints", "Endpoints", "{C41B35C8-A8CE-445B-A642-9139EE86427A}"
1784+
EndProject
1785+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.Endpoints", "src\Identity\Endpoints\src\Microsoft.AspNetCore.Identity.Endpoints.csproj", "{504CBD60-5CD7-4468-9A5A-8C11DECE268A}"
1786+
EndProject
1787+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.Endpoints", "src\Identity\samples\IdentitySample.Endpoints\IdentitySample.Endpoints.csproj", "{9F6A0033-5253-46FE-A51B-1813C49EC11F}"
1788+
EndProject
17831789
Global
17841790
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17851791
Debug|Any CPU = Debug|Any CPU
@@ -10701,6 +10707,38 @@ Global
1070110707
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x64.Build.0 = Release|Any CPU
1070210708
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x86.ActiveCfg = Release|Any CPU
1070310709
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}.Release|x86.Build.0 = Release|Any CPU
10710+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10711+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|Any CPU.Build.0 = Debug|Any CPU
10712+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|arm64.ActiveCfg = Debug|Any CPU
10713+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|arm64.Build.0 = Debug|Any CPU
10714+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|x64.ActiveCfg = Debug|Any CPU
10715+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|x64.Build.0 = Debug|Any CPU
10716+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|x86.ActiveCfg = Debug|Any CPU
10717+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Debug|x86.Build.0 = Debug|Any CPU
10718+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|Any CPU.ActiveCfg = Release|Any CPU
10719+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|Any CPU.Build.0 = Release|Any CPU
10720+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|arm64.ActiveCfg = Release|Any CPU
10721+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|arm64.Build.0 = Release|Any CPU
10722+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|x64.ActiveCfg = Release|Any CPU
10723+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|x64.Build.0 = Release|Any CPU
10724+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|x86.ActiveCfg = Release|Any CPU
10725+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A}.Release|x86.Build.0 = Release|Any CPU
10726+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10727+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|Any CPU.Build.0 = Debug|Any CPU
10728+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|arm64.ActiveCfg = Debug|Any CPU
10729+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|arm64.Build.0 = Debug|Any CPU
10730+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|x64.ActiveCfg = Debug|Any CPU
10731+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|x64.Build.0 = Debug|Any CPU
10732+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|x86.ActiveCfg = Debug|Any CPU
10733+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Debug|x86.Build.0 = Debug|Any CPU
10734+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|Any CPU.ActiveCfg = Release|Any CPU
10735+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|Any CPU.Build.0 = Release|Any CPU
10736+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|arm64.ActiveCfg = Release|Any CPU
10737+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|arm64.Build.0 = Release|Any CPU
10738+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|x64.ActiveCfg = Release|Any CPU
10739+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|x64.Build.0 = Release|Any CPU
10740+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|x86.ActiveCfg = Release|Any CPU
10741+
{9F6A0033-5253-46FE-A51B-1813C49EC11F}.Release|x86.Build.0 = Release|Any CPU
1070410742
EndGlobalSection
1070510743
GlobalSection(SolutionProperties) = preSolution
1070610744
HideSolutionNode = FALSE
@@ -11580,6 +11618,9 @@ Global
1158011618
{AE4D272D-6F13-42C8-9404-C149188AFA33} = {7BAEB9BF-28F4-4DFD-9A04-E5193683C261}
1158111619
{5D438258-CB19-4282-814F-974ABBC71411} = {7BAEB9BF-28F4-4DFD-9A04-E5193683C261}
1158211620
{F5AE525F-F435-40F9-A567-4D5EC3B50D6E} = {5FE1FBC1-8CE3-4355-9866-44FE1307C5F1}
11621+
{C41B35C8-A8CE-445B-A642-9139EE86427A} = {9F21A235-436E-4020-A076-1DF4F89D0CA0}
11622+
{504CBD60-5CD7-4468-9A5A-8C11DECE268A} = {C41B35C8-A8CE-445B-A642-9139EE86427A}
11623+
{9F6A0033-5253-46FE-A51B-1813C49EC11F} = {64B2A28F-6D82-4F2B-B0BB-88DE5216DD2C}
1158311624
EndGlobalSection
1158411625
GlobalSection(ExtensibilityGlobals) = postSolution
1158511626
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

eng/Dependencies.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ and are generated based on the last package release.
7373
<LatestPackageReference Include="System.Diagnostics.DiagnosticSource" />
7474
<LatestPackageReference Include="System.Diagnostics.EventLog" />
7575
<LatestPackageReference Include="System.DirectoryServices.Protocols" />
76-
<LatestPackageReference Include="System.IdentityModel.Tokens.Jwt" />
7776
<LatestPackageReference Include="System.IO.Pipelines" />
7877
<LatestPackageReference Include="System.Net.Http" />
7978
<LatestPackageReference Include="System.Net.Http.Json" />
@@ -153,6 +152,7 @@ and are generated based on the last package release.
153152
<LatestPackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" />
154153
<LatestPackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
155154
<LatestPackageReference Include="Microsoft.IdentityModel.Protocols.WsFederation" />
155+
<LatestPackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
156156
<LatestPackageReference Include="Microsoft.Internal.AspNetCore.H2Spec.All" />
157157
<LatestPackageReference Include="Microsoft.NETCore.Windows.ApiSets" />
158158
<LatestPackageReference Include="Microsoft.NETCore.BrowserDebugHost.Transport" />
@@ -163,6 +163,7 @@ and are generated based on the last package release.
163163
<LatestPackageReference Include="NETStandard.Library" />
164164
<LatestPackageReference Include="System.Net.Experimental.MsQuic" />
165165
<LatestPackageReference Include="System.Net.Http.WinHttpHandler" />
166+
<LatestPackageReference Include="System.IdentityModel.Tokens.Jwt" />
166167
<LatestPackageReference Include="System.ServiceProcess.ServiceController" />
167168
<LatestPackageReference Include="System.Threading.Tasks.Extensions" />
168169
</ItemGroup>

eng/ProjectReferences.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Html.Abstractions" ProjectPath="$(RepoRoot)src\Html.Abstractions\src\Microsoft.AspNetCore.Html.Abstractions.csproj" />
4040
<ProjectReferenceProvider Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" ProjectPath="$(RepoRoot)src\Identity\ApiAuthorization.IdentityServer\src\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj" />
4141
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity" ProjectPath="$(RepoRoot)src\Identity\Core\src\Microsoft.AspNetCore.Identity.csproj" />
42+
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity.Endpoints" ProjectPath="$(RepoRoot)src\Identity\Endpoints\src\Microsoft.AspNetCore.Identity.Endpoints.csproj" />
4243
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" ProjectPath="$(RepoRoot)src\Identity\EntityFrameworkCore\src\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj" />
4344
<ProjectReferenceProvider Include="Microsoft.Extensions.Identity.Core" ProjectPath="$(RepoRoot)src\Identity\Extensions.Core\src\Microsoft.Extensions.Identity.Core.csproj" />
4445
<ProjectReferenceProvider Include="Microsoft.Extensions.Identity.Stores" ProjectPath="$(RepoRoot)src\Identity\Extensions.Stores\src\Microsoft.Extensions.Identity.Stores.csproj" />

eng/TrimmableProjects.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<TrimmableProject Include="Microsoft.AspNetCore.WebUtilities" />
3434
<TrimmableProject Include="Microsoft.AspNetCore.Html.Abstractions" />
3535
<TrimmableProject Include="Microsoft.AspNetCore.Identity" />
36+
<TrimmableProject Include="Microsoft.AspNetCore.Identity.Endpoints" />
3637
<TrimmableProject Include="Microsoft.Extensions.Identity.Core" />
3738
<TrimmableProject Include="Microsoft.Extensions.Identity.Stores" />
3839
<TrimmableProject Include="Microsoft.AspNetCore.Connections.Abstractions" />

eng/Versions.props

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,10 @@
220220
<MicrosoftCodeAnalysisCSharpAnalyzerTestingXUnitVersion>1.1.2-beta1.22531.1</MicrosoftCodeAnalysisCSharpAnalyzerTestingXUnitVersion>
221221
<MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion>1.1.2-beta1.22531.1</MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion>
222222
<MicrosoftCssParserVersion>1.0.0-20200708.1</MicrosoftCssParserVersion>
223-
<MicrosoftIdentityModelLoggingVersion>6.15.1</MicrosoftIdentityModelLoggingVersion>
224-
<MicrosoftIdentityModelProtocolsOpenIdConnectVersion>6.15.1</MicrosoftIdentityModelProtocolsOpenIdConnectVersion>
225-
<MicrosoftIdentityModelProtocolsWsFederationVersion>6.15.1</MicrosoftIdentityModelProtocolsWsFederationVersion>
223+
<MicrosoftIdentityModelJsonWebTokensVersion>6.27.0</MicrosoftIdentityModelJsonWebTokensVersion>
224+
<MicrosoftIdentityModelLoggingVersion>6.27.0</MicrosoftIdentityModelLoggingVersion>
225+
<MicrosoftIdentityModelProtocolsOpenIdConnectVersion>6.27.0</MicrosoftIdentityModelProtocolsOpenIdConnectVersion>
226+
<MicrosoftIdentityModelProtocolsWsFederationVersion>6.27.0</MicrosoftIdentityModelProtocolsWsFederationVersion>
226227
<MicrosoftInternalAspNetCoreH2SpecAllVersion>2.2.1</MicrosoftInternalAspNetCoreH2SpecAllVersion>
227228
<MicrosoftNETCoreWindowsApiSetsVersion>1.0.1</MicrosoftNETCoreWindowsApiSetsVersion>
228229
<MicrosoftOwinSecurityCookiesVersion>3.0.1</MicrosoftOwinSecurityCookiesVersion>

src/Identity/Core/src/IdentityConstants.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,30 @@ namespace Microsoft.AspNetCore.Identity;
88
/// </summary>
99
public class IdentityConstants
1010
{
11-
private const string CookiePrefix = "Identity";
11+
private const string IdentityPrefix = "Identity";
1212

1313
/// <summary>
1414
/// The scheme used to identify application authentication cookies.
1515
/// </summary>
16-
public static readonly string ApplicationScheme = CookiePrefix + ".Application";
16+
public static readonly string ApplicationScheme = IdentityPrefix + ".Application";
17+
18+
/// <summary>
19+
/// The scheme used to identify bearer authentication tokens.
20+
/// </summary>
21+
public static readonly string BearerScheme = IdentityPrefix + ".Bearer";
1722

1823
/// <summary>
1924
/// The scheme used to identify external authentication cookies.
2025
/// </summary>
21-
public static readonly string ExternalScheme = CookiePrefix + ".External";
26+
public static readonly string ExternalScheme = IdentityPrefix + ".External";
2227

2328
/// <summary>
2429
/// The scheme used to identify Two Factor authentication cookies for saving the Remember Me state.
2530
/// </summary>
26-
public static readonly string TwoFactorRememberMeScheme = CookiePrefix + ".TwoFactorRememberMe";
31+
public static readonly string TwoFactorRememberMeScheme = IdentityPrefix + ".TwoFactorRememberMe";
2732

2833
/// <summary>
2934
/// The scheme used to identify Two Factor authentication cookies for round tripping user identities.
3035
/// </summary>
31-
public static readonly string TwoFactorUserIdScheme = CookiePrefix + ".TwoFactorUserId";
36+
public static readonly string TwoFactorUserIdScheme = IdentityPrefix + ".TwoFactorUserId";
3237
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#nullable enable
2+
static readonly Microsoft.AspNetCore.Identity.IdentityConstants.BearerScheme -> string!
23
virtual Microsoft.AspNetCore.Identity.SignInManager<TUser>.IsTwoFactorEnabledAsync(TUser! user) -> System.Threading.Tasks.Task<bool>!
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http.Json;
5+
using Microsoft.AspNetCore.Identity.Endpoints.DTO;
6+
using Microsoft.Extensions.Options;
7+
8+
namespace Microsoft.AspNetCore.Identity.Endpoints;
9+
10+
// Review: This is trying to do a similar thing to ProblemDetailsOptionsSetup.
11+
// Is this what we landed on for now? Should we update that too? AddContext will be obsolete soon.
12+
// See: https://github.com/dotnet/runtime/issues/83280
13+
internal sealed class PostConfigureIdentityEndpointJsonOptions : IPostConfigureOptions<JsonOptions>
14+
{
15+
public void PostConfigure(string? name, JsonOptions options)
16+
{
17+
// Add our resolver first so it isn't overridden by the default reflection-based resolver.
18+
options.SerializerOptions.TypeInfoResolverChain.Insert(0, IdentityEndpointJsonSerializerContext.Default);
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Microsoft.AspNetCore.Identity.Endpoints.DTO;
7+
8+
internal sealed class AccessTokenResponse
9+
{
10+
[JsonPropertyName("token_type")]
11+
public string TokenType { get; } = "Bearer";
12+
13+
[JsonPropertyName("access_token")]
14+
public required string AccessToken { get; init; }
15+
16+
[JsonPropertyName("expires_in")]
17+
public required double ExpiresInTotalSeconds { get; init; }
18+
19+
// TODO: [JsonPropertyName("refresh_token")]
20+
// public required string RefreshToken { get; init; }
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Microsoft.AspNetCore.Identity.Endpoints.DTO;
7+
8+
[JsonSerializable(typeof(RegisterRequest))]
9+
[JsonSerializable(typeof(LoginRequest))]
10+
[JsonSerializable(typeof(AccessTokenResponse))]
11+
internal sealed partial class IdentityEndpointJsonSerializerContext : JsonSerializerContext
12+
{
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Identity.Endpoints.DTO;
5+
6+
internal sealed class LoginRequest
7+
{
8+
public required string Username { get; init; }
9+
public required string Password { get; init; }
10+
public bool CookieMode { get; init; }
11+
// TODO: public string? TfaCode { get; set; }
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Identity.Endpoints.DTO;
5+
6+
// TODO: Register DTOs with JsonSerializerOptions.TypeInfoResolverChain (was previously the soon-to-be-obsolete AddContext)
7+
internal sealed class RegisterRequest
8+
{
9+
public required string Username { get; init; }
10+
public required string Password { get; init; }
11+
// TODO: public string? Email { get; set; }
12+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Security.Claims;
5+
using System.Text.Encodings.Web;
6+
using Microsoft.AspNetCore.Authentication;
7+
using Microsoft.AspNetCore.DataProtection;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Identity.Endpoints.DTO;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Options;
12+
using Microsoft.Net.Http.Headers;
13+
14+
namespace Microsoft.AspNetCore.Identity.Endpoints;
15+
16+
internal sealed class IdentityBearerAuthenticationHandler : SignInAuthenticationHandler<IdentityBearerAuthenticationOptions>
17+
{
18+
private const string BearerTokenPurpose = $"Microsoft.AspNetCore.Identity.Endpoints.IdentityBearerAuthenticationHandler:v1:BearerToken";
19+
20+
private static readonly Task<AuthenticateResult> TokenMissingTask = Task.FromResult(AuthenticateResult.Fail("Token missing"));
21+
private static readonly Task<AuthenticateResult> FailedUnprotectingTokenTask = Task.FromResult(AuthenticateResult.Fail("Unprotect token failed"));
22+
private static readonly Task<AuthenticateResult> TokenExpiredTask = Task.FromResult(AuthenticateResult.Fail("Token expired"));
23+
24+
private readonly IDataProtectionProvider _fallbackDataProtectionProvider;
25+
26+
public IdentityBearerAuthenticationHandler(
27+
IOptionsMonitor<IdentityBearerAuthenticationOptions> optionsMonitor,
28+
ILoggerFactory loggerFactory,
29+
UrlEncoder urlEncoder,
30+
ISystemClock clock,
31+
IDataProtectionProvider dataProtectionProvider)
32+
: base(optionsMonitor, loggerFactory, urlEncoder, clock)
33+
{
34+
_fallbackDataProtectionProvider = dataProtectionProvider;
35+
}
36+
37+
private IDataProtectionProvider DataProtectionProvider
38+
=> Options.DataProtectionProvider ?? _fallbackDataProtectionProvider;
39+
40+
private ISecureDataFormat<AuthenticationTicket> BearerTokenProtector
41+
=> Options.BearerTokenProtector ?? new TicketDataFormat(DataProtectionProvider.CreateProtector(BearerTokenPurpose));
42+
43+
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
44+
{
45+
// If there's no bearer token, forward to cookie auth.
46+
if (GetBearerTokenOrNull() is not string token)
47+
{
48+
return Options.BearerTokenMissingFallbackScheme is string fallbackScheme
49+
? Context.AuthenticateAsync(fallbackScheme)
50+
: TokenMissingTask;
51+
}
52+
53+
var ticket = BearerTokenProtector.Unprotect(token);
54+
55+
if (ticket?.Properties?.ExpiresUtc is null)
56+
{
57+
return FailedUnprotectingTokenTask;
58+
}
59+
60+
if (Clock.UtcNow >= ticket.Properties.ExpiresUtc)
61+
{
62+
return TokenExpiredTask;
63+
}
64+
65+
return Task.FromResult(AuthenticateResult.Success(ticket));
66+
}
67+
68+
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
69+
{
70+
// If there's no bearer token, forward to cookie auth.
71+
if (GetBearerTokenOrNull() is null)
72+
{
73+
return Options.BearerTokenMissingFallbackScheme is string fallbackScheme
74+
? Context.AuthenticateAsync(fallbackScheme)
75+
: TokenMissingTask;
76+
}
77+
78+
Response.Headers.Append(HeaderNames.WWWAuthenticate, "Bearer");
79+
return base.HandleChallengeAsync(properties);
80+
}
81+
82+
protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
83+
{
84+
properties ??= new();
85+
properties.ExpiresUtc ??= Clock.UtcNow + Options.BearerTokenExpiration;
86+
87+
var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
88+
var accessTokenResponse = new AccessTokenResponse
89+
{
90+
AccessToken = BearerTokenProtector.Protect(ticket),
91+
ExpiresInTotalSeconds = Options.BearerTokenExpiration.TotalSeconds,
92+
};
93+
94+
return Context.Response.WriteAsJsonAsync(accessTokenResponse);
95+
}
96+
97+
protected override Task HandleSignOutAsync(AuthenticationProperties? properties)
98+
=> throw new NotSupportedException($"""
99+
Sign out is not currently supported by identity bearer tokens.
100+
If you want to delete cookies or clear a session, specify "{Options.BearerTokenMissingFallbackScheme}" as the authentication scheme.
101+
""");
102+
103+
private string? GetBearerTokenOrNull()
104+
{
105+
var authorization = Request.Headers.Authorization.ToString();
106+
107+
return authorization.StartsWith("Bearer ", StringComparison.Ordinal)
108+
? authorization["Bearer ".Length..]
109+
: null;
110+
}
111+
}

0 commit comments

Comments
 (0)