Skip to content

Commit f32a1b4

Browse files
committed
Add BearerTokenEvents
1 parent 9b016bb commit f32a1b4

File tree

6 files changed

+93
-19
lines changed

6 files changed

+93
-19
lines changed

src/Identity/test/Identity.FunctionalTests/MapIdentityTests.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,10 @@ public async Task CanReadBearerTokenFromQueryString()
157157
services.AddIdentityCore<ApplicationUser>().AddApiEndpoints().AddEntityFrameworkStores<ApplicationDbContext>();
158158
services.AddAuthentication(IdentityConstants.BearerScheme).AddBearerToken(IdentityConstants.BearerScheme, options =>
159159
{
160-
options.ExtractBearerToken = context =>
160+
options.Events.OnMessageReceived = context =>
161161
{
162-
var bearerToken = context.Request.Query["access_token"];
163-
return StringValues.IsNullOrEmpty(bearerToken)
164-
? default
165-
: new(bearerToken.ToString());
162+
context.Token = (string?)context.Request.Query["access_token"];
163+
return Task.CompletedTask;
166164
};
167165
});
168166
});
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+
namespace Microsoft.AspNetCore.Authentication.BearerToken;
5+
6+
/// <summary>
7+
/// Specifies events which the bearer token handler invokes to enable developer control over the authentication process.
8+
/// </summary>
9+
public class BearerTokenEvents
10+
{
11+
/// <summary>
12+
/// Invoked when a protocol message is first received.
13+
/// </summary>
14+
public Func<MessageReceivedContext, Task> OnMessageReceived { get; set; } = context => Task.CompletedTask;
15+
16+
/// <summary>
17+
/// Invoked when a protocol message is first received.
18+
/// </summary>
19+
public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context);
20+
}

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,23 @@ internal sealed class BearerTokenHandler(
2929
private ISecureDataFormat<AuthenticationTicket> BearerTokenProtector
3030
=> Options.BearerTokenProtector ?? new TicketDataFormat(dataProtectionProvider.CreateProtector(BearerTokenPurpose));
3131

32+
private new BearerTokenEvents Events => (BearerTokenEvents)base.Events!;
33+
3234
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
3335
{
34-
// If there's no bearer token, forward to cookie auth.
35-
if (await GetBearerTokenOrNullAsync() is not string token)
36+
// Give application opportunity to find from a different location, adjust, or reject token
37+
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
38+
39+
await Events.MessageReceived(messageReceivedContext);
40+
41+
if (messageReceivedContext.Result != null)
42+
{
43+
return messageReceivedContext.Result;
44+
}
45+
46+
var token = messageReceivedContext.Token ?? GetBearerTokenOrNull();
47+
48+
if (token is null)
3649
{
3750
return AuthenticateResult.NoResult();
3851
}
@@ -76,14 +89,8 @@ protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationPr
7689
// No-op to avoid interfering with any mass sign-out logic.
7790
protected override Task HandleSignOutAsync(AuthenticationProperties? properties) => Task.CompletedTask;
7891

79-
private async ValueTask<string?> GetBearerTokenOrNullAsync()
92+
private string? GetBearerTokenOrNull()
8093
{
81-
if (Options.ExtractBearerToken is not null &&
82-
await Options.ExtractBearerToken(Context) is string token)
83-
{
84-
return token;
85-
}
86-
8794
var authorization = Request.Headers.Authorization.ToString();
8895

8996
return authorization.StartsWith("Bearer ", StringComparison.Ordinal)

src/Security/Authentication/BearerToken/src/BearerTokenOptions.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.DataProtection;
5-
using Microsoft.AspNetCore.Http;
65

76
namespace Microsoft.AspNetCore.Authentication.BearerToken;
87

@@ -11,6 +10,14 @@ namespace Microsoft.AspNetCore.Authentication.BearerToken;
1110
/// </summary>
1211
public sealed class BearerTokenOptions : AuthenticationSchemeOptions
1312
{
13+
/// <summary>
14+
/// Constructs the options used to authenticate using opaque bearer tokens.
15+
/// </summary>
16+
public BearerTokenOptions()
17+
{
18+
Events = new();
19+
}
20+
1421
/// <summary>
1522
/// Controls how much time the bearer token will remain valid from the point it is created.
1623
/// The expiration information is stored in the protected token. Because of that, an expired token will be rejected
@@ -26,8 +33,14 @@ public sealed class BearerTokenOptions : AuthenticationSchemeOptions
2633
public ISecureDataFormat<AuthenticationTicket>? BearerTokenProtector { get; set; }
2734

2835
/// <summary>
29-
/// If set, this provides the bearer token. If unset, the bearer token is read from the Authorization request header with a "Bearer " prefix.
36+
/// The object provided by the application to process events raised by the bearer token authentication handler.
37+
/// The application may implement the interface fully, or it may create an instance of <see cref="BearerTokenEvents"/>
38+
/// and assign delegates only to the events it wants to process.
3039
/// </summary>
31-
public Func<HttpContext, ValueTask<string?>>? ExtractBearerToken { get; set; }
40+
public new BearerTokenEvents Events
41+
{
42+
get { return (BearerTokenEvents)base.Events!; }
43+
set { base.Events = value; }
44+
}
3245
}
3346

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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;
5+
6+
namespace Microsoft.AspNetCore.Authentication.BearerToken;
7+
8+
/// <summary>
9+
/// A context for <see cref="BearerTokenEvents.OnMessageReceived"/>.
10+
/// </summary>
11+
public class MessageReceivedContext : ResultContext<BearerTokenOptions>
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of <see cref="MessageReceivedContext"/>.
15+
/// </summary>
16+
/// <inheritdoc />
17+
public MessageReceivedContext(
18+
HttpContext context,
19+
AuthenticationScheme scheme,
20+
BearerTokenOptions options)
21+
: base(context, scheme, options) { }
22+
23+
/// <summary>
24+
/// Bearer Token. This will give the application an opportunity to retrieve a token from an alternative location.
25+
/// </summary>
26+
public string? Token { get; set; }
27+
}
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
#nullable enable
22
const Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenDefaults.AuthenticationScheme = "BearerToken" -> string!
33
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenDefaults
4+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents
5+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents.BearerTokenEvents() -> void
6+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents.OnMessageReceived.get -> System.Func<Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext!, System.Threading.Tasks.Task!>!
7+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents.OnMessageReceived.set -> void
48
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions
59
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.BearerTokenExpiration.get -> System.TimeSpan
610
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.BearerTokenExpiration.set -> void
711
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.BearerTokenOptions() -> void
812
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.BearerTokenProtector.get -> Microsoft.AspNetCore.Authentication.ISecureDataFormat<Microsoft.AspNetCore.Authentication.AuthenticationTicket!>?
913
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.BearerTokenProtector.set -> void
10-
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.ExtractBearerToken.get -> System.Func<Microsoft.AspNetCore.Http.HttpContext!, System.Threading.Tasks.ValueTask<string?>>?
11-
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.ExtractBearerToken.set -> void
14+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.Events.get -> Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents!
15+
Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions.Events.set -> void
16+
Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext
17+
Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext.MessageReceivedContext(Microsoft.AspNetCore.Http.HttpContext! context, Microsoft.AspNetCore.Authentication.AuthenticationScheme! scheme, Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions! options) -> void
18+
Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext.Token.get -> string?
19+
Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext.Token.set -> void
1220
Microsoft.Extensions.DependencyInjection.BearerTokenExtensions
1321
static Microsoft.Extensions.DependencyInjection.BearerTokenExtensions.AddBearerToken(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!
1422
static Microsoft.Extensions.DependencyInjection.BearerTokenExtensions.AddBearerToken(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder, string! authenticationScheme) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!
1523
static Microsoft.Extensions.DependencyInjection.BearerTokenExtensions.AddBearerToken(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder, string! authenticationScheme, System.Action<Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions!>! configure) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!
1624
static Microsoft.Extensions.DependencyInjection.BearerTokenExtensions.AddBearerToken(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! builder, System.Action<Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenOptions!>! configure) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder!
25+
virtual Microsoft.AspNetCore.Authentication.BearerToken.BearerTokenEvents.MessageReceived(Microsoft.AspNetCore.Authentication.BearerToken.MessageReceivedContext! context) -> System.Threading.Tasks.Task!

0 commit comments

Comments
 (0)