Skip to content

Commit 1d2c90a

Browse files
authored
React to MapIdentityApi HTTP endpoints API review feedback (#50067)
1 parent e8156e8 commit 1d2c90a

File tree

7 files changed

+162
-91
lines changed

7 files changed

+162
-91
lines changed

src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,13 @@ public static IEndpointConventionBuilder MapIdentityApi<TUser>(this IEndpointRou
9090
});
9191

9292
routeGroup.MapPost("/login", async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>>
93-
([FromBody] LoginRequest login, [FromQuery] bool? cookieMode, [FromQuery] bool? persistCookies, [FromServices] IServiceProvider sp) =>
93+
([FromBody] LoginRequest login, [FromQuery] bool? useCookies, [FromQuery] bool? useSessionCookies, [FromServices] IServiceProvider sp) =>
9494
{
9595
var signInManager = sp.GetRequiredService<SignInManager<TUser>>();
9696

97-
signInManager.PrimaryAuthenticationScheme = cookieMode == true ? IdentityConstants.ApplicationScheme : IdentityConstants.BearerScheme;
98-
var isPersistent = persistCookies ?? true;
97+
var useCookieScheme = (useCookies == true) || (useSessionCookies == true);
98+
var isPersistent = (useCookies == true) && (useSessionCookies != true);
99+
signInManager.PrimaryAuthenticationScheme = useCookieScheme ? IdentityConstants.ApplicationScheme : IdentityConstants.BearerScheme;
99100

100101
var result = await signInManager.PasswordSignInAsync(login.Email, login.Password, isPersistent, lockoutOnFailure: true);
101102

@@ -258,7 +259,7 @@ await emailSender.SendEmailAsync(resetRequest.Email, "Reset your password",
258259
return TypedResults.Ok();
259260
});
260261

261-
var accountGroup = routeGroup.MapGroup("/account").RequireAuthorization();
262+
var accountGroup = routeGroup.MapGroup("/manage").RequireAuthorization();
262263

263264
accountGroup.MapPost("/2fa", async Task<Results<Ok<TwoFactorResponse>, ValidationProblem, NotFound>>
264265
(ClaimsPrincipal claimsPrincipal, [FromBody] TwoFactorRequest tfaRequest, [FromServices] IServiceProvider sp) =>

src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs

Lines changed: 119 additions & 75 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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.Authentication.BearerToken.DTO;
5+
using Microsoft.AspNetCore.Http.Json;
6+
using Microsoft.Extensions.Options;
7+
8+
namespace Microsoft.AspNetCore.Authentication.BearerToken;
9+
10+
internal sealed class BearerTokenConfigureJsonOptions : IConfigureOptions<JsonOptions>
11+
{
12+
public void Configure(JsonOptions options)
13+
{
14+
// Put our resolver in front of the reflection-based one. See ProblemDetailsOptionsSetup for a detailed explanation.
15+
options.SerializerOptions.TypeInfoResolverChain.Insert(0, BearerTokenJsonSerializerContext.Default);
16+
}
17+
}

src/Security/Authentication/BearerToken/src/BearerTokenExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.AspNetCore.Authentication;
55
using Microsoft.AspNetCore.Authentication.BearerToken;
6+
using Microsoft.AspNetCore.Http.Json;
67
using Microsoft.Extensions.DependencyInjection.Extensions;
78
using Microsoft.Extensions.Options;
89

@@ -64,6 +65,7 @@ public static AuthenticationBuilder AddBearerToken(this AuthenticationBuilder bu
6465
ArgumentNullException.ThrowIfNull(authenticationScheme);
6566
ArgumentNullException.ThrowIfNull(configure);
6667

68+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<JsonOptions>, BearerTokenConfigureJsonOptions>());
6769
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<BearerTokenOptions>, BearerTokenConfigureOptions>());
6870
return builder.AddScheme<BearerTokenOptions, BearerTokenHandler>(authenticationScheme, configure);
6971
}

src/Security/Authentication/BearerToken/src/BearerTokenHandler.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
using System.Security.Claims;
55
using System.Text.Encodings.Web;
6+
using System.Text.Json.Serialization.Metadata;
67
using Microsoft.AspNetCore.Authentication.BearerToken.DTO;
78
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Http.Json;
10+
using Microsoft.Extensions.DependencyInjection;
811
using Microsoft.Extensions.Logging;
912
using Microsoft.Extensions.Options;
1013
using Microsoft.Net.Http.Headers;
@@ -69,13 +72,21 @@ protected override async Task HandleSignInAsync(ClaimsPrincipal user, Authentica
6972
var response = new AccessTokenResponse
7073
{
7174
AccessToken = Options.BearerTokenProtector.Protect(CreateBearerTicket(user, properties)),
72-
ExpiresInSeconds = (long)Options.BearerTokenExpiration.TotalSeconds,
75+
ExpiresIn = (long)Options.BearerTokenExpiration.TotalSeconds,
7376
RefreshToken = Options.RefreshTokenProtector.Protect(CreateRefreshTicket(user, utcNow)),
7477
};
7578

7679
Logger.AuthenticationSchemeSignedIn(Scheme.Name);
7780

78-
await Context.Response.WriteAsJsonAsync(response, BearerTokenJsonSerializerContext.Default.AccessTokenResponse);
81+
await Context.Response.WriteAsJsonAsync(response, ResolveAccessTokenJsonTypeInfo(Context));
82+
}
83+
84+
private static JsonTypeInfo<AccessTokenResponse> ResolveAccessTokenJsonTypeInfo(HttpContext httpContext)
85+
{
86+
// Attempt to resolve options from DI then fall back to static options
87+
var typeInfo = httpContext.RequestServices.GetService<IOptions<JsonOptions>>()
88+
?.Value?.SerializerOptions?.GetTypeInfo(typeof(AccessTokenResponse)) as JsonTypeInfo<AccessTokenResponse>;
89+
return typeInfo ?? BearerTokenJsonSerializerContext.Default.AccessTokenResponse;
7990
}
8091

8192
// No-op to avoid interfering with any mass sign-out logic.
Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Text.Json.Serialization;
4+
using System.Text.Json;
55

66
namespace Microsoft.AspNetCore.Authentication.BearerToken.DTO;
77

@@ -15,35 +15,31 @@ internal sealed class AccessTokenResponse
1515
/// in the form of an opaque <see cref="AccessToken"/>.
1616
/// </summary>
1717
/// <remarks>
18-
/// This is serialized as "token_type": "Bearer" using System.Text.Json.
18+
/// This is serialized as "tokenType": "Bearer" using <see cref="JsonSerializerDefaults.Web"/>.
1919
/// </remarks>
20-
[JsonPropertyName("token_type")]
2120
public string TokenType { get; } = "Bearer";
2221

2322
/// <summary>
2423
/// The opaque bearer token to send as part of the Authorization request header.
2524
/// </summary>
2625
/// <remarks>
27-
/// This is serialized as "access_token": "{AccessToken}" using System.Text.Json.
26+
/// This is serialized as "accessToken": "{AccessToken}" using <see cref="JsonSerializerDefaults.Web"/>.
2827
/// </remarks>
29-
[JsonPropertyName("access_token")]
3028
public required string AccessToken { get; init; }
3129

3230
/// <summary>
3331
/// The number of seconds before the <see cref="AccessToken"/> expires.
3432
/// </summary>
3533
/// <remarks>
36-
/// This is serialized as "expires_in": "{ExpiresInSeconds}" using System.Text.Json.
34+
/// This is serialized as "expiresIn": "{ExpiresInSeconds}" using <see cref="JsonSerializerDefaults.Web"/>.
3735
/// </remarks>
38-
[JsonPropertyName("expires_in")]
39-
public required long ExpiresInSeconds { get; init; }
36+
public required long ExpiresIn { get; init; }
4037

4138
/// <summary>
4239
/// If set, this provides the ability to get a new access_token after it expires using a refresh endpoint.
4340
/// </summary>
4441
/// <remarks>
45-
/// This is serialized as "refresh_token": "{RefreshToken}" using System.Text.Json.
42+
/// This is serialized as "refreshToken": "{RefreshToken}" using using <see cref="JsonSerializerDefaults.Web"/>.
4643
/// </remarks>
47-
[JsonPropertyName("refresh_token")]
4844
public required string RefreshToken { get; init; }
4945
}

0 commit comments

Comments
 (0)