Skip to content

Commit 9eb1121

Browse files
authored
Merge pull request #17 from Shuttle/session-token-claim
Session token claim
2 parents 64d3186 + 53d294f commit 9eb1121

File tree

64 files changed

+575
-386
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+575
-386
lines changed
Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
<?xml version="1.0"?>
22

33
<package>
4-
<metadata>
5-
<id>Shuttle.Access.Application</id>
6-
<version>6.0.0</version>
7-
<authors>Eben Roux</authors>
8-
<owners>Eben Roux</owners>
9-
<license type="expression">BSD-3-Clause</license>
10-
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11-
<icon>images\logo.png</icon>
12-
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
13-
<projectUrl>https://github.com/shuttle/Shuttle.Access</projectUrl>
14-
<description>A client component that calls the Shuttle.Access.WebApi to interact with the Shuttle.Access Identity and Access Management domain.</description>
15-
<copyright>Copyright (c) 2025, Eben Roux</copyright>
16-
<tags>iam identity authorization authentication security permissions</tags>
17-
<dependencies>
18-
<dependency id="Microsoft.CSharp" version="4.7.0" />
4+
<metadata>
5+
<id>Shuttle.Access.Application</id>
6+
<version>6.0.2</version>
7+
<authors>Eben Roux</authors>
8+
<owners>Eben Roux</owners>
9+
<license type="expression">BSD-3-Clause</license>
10+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11+
<icon>images\logo.png</icon>
12+
<readme>docs\README.md</readme>
13+
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14+
<projectUrl>https://github.com/shuttle/Shuttle.Access</projectUrl>
15+
<description>Application concern implementations such as Shuttle.Core.Mediator participants.</description>
16+
<copyright>Copyright (c) 2025, Eben Roux</copyright>
17+
<tags>iam identity authorization authentication security permissions</tags>
18+
<dependencies>
19+
<dependency id="Microsoft.CSharp" version="4.7.0" />
1920
<dependency id="Shuttle.Core.Contract" version="20.0.0" />
2021
<dependency id="Shuttle.Core.Mediator" version="20.0.0" />
2122
<dependency id="Shuttle.Esb" version="20.0.0" />
2223
<dependency id="Shuttle.Recall" version="20.0.0" />
2324
<dependency id="Shuttle.Recall.Sql.Storage" version="20.0.0" />
24-
</dependencies>
25-
</metadata>
26-
<files>
27-
<file src="..\..\..\.media\logo.png" target="images" />
28-
<file src="lib\**\*.*" target="lib" />
29-
</files>
25+
</dependencies>
26+
</metadata>
27+
<files>
28+
<file src="..\..\..\.media\logo.png" target="images" />
29+
<file src="..\..\..\README.md" target="docs\" />
30+
<file src="lib\**\*.*" target="lib" />
31+
</files>
3032
</package>
Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
<?xml version="1.0"?>
22

33
<package>
4-
<metadata>
5-
<id>Shuttle.Access.Application</id>
6-
<version>#{SemanticVersion}#</version>
7-
<authors>Eben Roux</authors>
8-
<owners>Eben Roux</owners>
9-
<license type="expression">BSD-3-Clause</license>
10-
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11-
<icon>images\logo.png</icon>
12-
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
13-
<projectUrl>https://github.com/shuttle/Shuttle.Access</projectUrl>
14-
<description>A client component that calls the Shuttle.Access.WebApi to interact with the Shuttle.Access Identity and Access Management domain.</description>
15-
<copyright>Copyright (c) #{Year}#, Eben Roux</copyright>
16-
<tags>iam identity authorization authentication security permissions</tags>
17-
<dependencies>
18-
#{Dependencies}#
19-
</dependencies>
20-
</metadata>
21-
<files>
22-
<file src="..\..\..\.media\logo.png" target="images" />
23-
<file src="lib\**\*.*" target="lib" />
24-
</files>
4+
<metadata>
5+
<id>Shuttle.Access.Application</id>
6+
<version>#{SemanticVersion}#</version>
7+
<authors>Eben Roux</authors>
8+
<owners>Eben Roux</owners>
9+
<license type="expression">BSD-3-Clause</license>
10+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11+
<icon>images\logo.png</icon>
12+
<readme>docs\README.md</readme>
13+
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14+
<projectUrl>https://github.com/shuttle/Shuttle.Access</projectUrl>
15+
<description>Application concern implementations such as Shuttle.Core.Mediator participants.</description>
16+
<copyright>Copyright (c) #{Year}#, Eben Roux</copyright>
17+
<tags>iam identity authorization authentication security permissions</tags>
18+
<dependencies>
19+
#{Dependencies}#
20+
</dependencies>
21+
</metadata>
22+
<files>
23+
<file src="..\..\..\.media\logo.png" target="images" />
24+
<file src="..\..\..\README.md" target="docs\" />
25+
<file src="lib\**\*.*" target="lib" />
26+
</files>
2527
</package>

Shuttle.Access.Application/ActivateIdentityParticipant.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public async Task ProcessMessageAsync(IParticipantContext<RequestResponseMessage
2525
Guard.AgainstNull(context);
2626

2727
var message = context.Message.Request;
28-
var now = DateTime.UtcNow;
28+
var now = DateTimeOffset.UtcNow;
2929

3030
var specification = new DataAccess.Identity.Specification();
3131

Shuttle.Access.Application/ConfigureApplicationParticipant.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
4-
using Castle.Core.Logging;
54
using Microsoft.Extensions.Logging;
65
using Shuttle.Access.DataAccess;
76
using Shuttle.Access.Messages.v1;
@@ -66,9 +65,9 @@ public async Task ProcessMessageAsync(IParticipantContext<ConfigureApplication>
6665

6766
await _mediator.SendAsync(registerRoleMessage);
6867

69-
var timeout = DateTime.Now.AddSeconds(15);
68+
var timeout = DateTimeOffset.Now.AddSeconds(15);
7069

71-
while (await _roleQuery.CountAsync(roleSpecification) == 0 && DateTime.Now < timeout)
70+
while (await _roleQuery.CountAsync(roleSpecification) == 0 && DateTimeOffset.Now < timeout)
7271
{
7372
Task.Delay(TimeSpan.FromMilliseconds(500), context.CancellationToken).Wait();
7473
}

Shuttle.Access.Application/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
[assembly: AssemblyTitle(".NET Unified Platform")]
1010
#endif
1111

12-
[assembly: AssemblyVersion("6.0.0.0")]
12+
[assembly: AssemblyVersion("6.0.2.0")]
1313
[assembly: AssemblyCopyright("Copyright (c) 2025, Eben Roux")]
1414
[assembly: AssemblyProduct("Shuttle.Access.Application")]
1515
[assembly: AssemblyCompany("Eben Roux")]
1616
[assembly: AssemblyConfiguration("Release")]
17-
[assembly: AssemblyInformationalVersion("6.0.0")]
17+
[assembly: AssemblyInformationalVersion("6.0.2")]
1818
[assembly: ComVisible(false)]

Shuttle.Access.Application/RefreshSessionParticipant.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public async Task ProcessMessageAsync(IParticipantContext<RefreshSession> contex
6363

6464
await _sessionRepository.SaveAsync(session);
6565

66-
_accessService.Flush(session.Token);
66+
await _accessService.RemoveAsync(session.Token);
6767

6868
await _serviceBus.PublishAsync(new SessionRefreshed
6969
{

Shuttle.Access.Application/RegisterSessionParticipant.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ public async Task ProcessMessageAsync(IParticipantContext<RegisterSession> conte
106106
return;
107107
}
108108

109-
if (session.ExpiryDate.Add(_accessOptions.SessionRenewalTolerance) > DateTime.UtcNow)
109+
if (session.ExpiryDate.Add(_accessOptions.SessionRenewalTolerance) > DateTimeOffset.UtcNow)
110110
{
111-
session.Renew(DateTime.UtcNow.Add(_accessOptions.SessionDuration));
111+
session.Renew(DateTimeOffset.UtcNow.Add(_accessOptions.SessionDuration));
112112

113113
await SaveAsync();
114114

@@ -120,7 +120,7 @@ public async Task ProcessMessageAsync(IParticipantContext<RegisterSession> conte
120120

121121
if (message.RegistrationType != SessionRegistrationType.Token)
122122
{
123-
var now = DateTime.UtcNow;
123+
var now = DateTimeOffset.UtcNow;
124124

125125
session = new(Guid.NewGuid(), await _identityQuery.IdAsync(message.IdentityName, context.CancellationToken), message.IdentityName, now, now.Add(_accessOptions.SessionDuration));
126126

@@ -142,7 +142,7 @@ async Task SaveAsync()
142142

143143
if (message.HasKnownApplicationOptions)
144144
{
145-
var sessionTokenExchange = new SessionTokenExchange(Guid.NewGuid(), session.Token, DateTime.UtcNow.Add(_accessOptions.SessionTokenExchangeValidityTimeSpan));
145+
var sessionTokenExchange = new SessionTokenExchange(Guid.NewGuid(), session.Token, DateTimeOffset.UtcNow.Add(_accessOptions.SessionTokenExchangeValidityTimeSpan));
146146

147147
await _sessionTokenExchangeRepository.SaveAsync(sessionTokenExchange, context.CancellationToken);
148148

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
<?xml version="1.0"?>
22

33
<package>
4-
<metadata>
5-
<id>Shuttle.Access.AspNetCore</id>
6-
<version>6.0.0</version>
7-
<authors>Eben Roux</authors>
8-
<owners>Eben Roux</owners>
9-
<license type="expression">BSD-3-Clause</license>
10-
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11-
<icon>images\logo.png</icon>
12-
<readme>docs\README.md</readme>
13-
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14-
<projectUrl>https://github.com/Shuttle/Shuttle.Access</projectUrl>
15-
<description>Authorization middleware for web API endpoints using Shuttle.Access.</description>
16-
<copyright>Copyright (c) 2025, Eben Roux</copyright>
17-
<tags>iam middleware authorization permissions</tags>
18-
<dependencies>
19-
<dependency id="Shuttle.Access" version="6.0.0" />
4+
<metadata>
5+
<id>Shuttle.Access.AspNetCore</id>
6+
<version>6.0.2</version>
7+
<authors>Eben Roux</authors>
8+
<owners>Eben Roux</owners>
9+
<license type="expression">BSD-3-Clause</license>
10+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11+
<icon>images\logo.png</icon>
12+
<readme>docs\README.md</readme>
13+
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14+
<projectUrl>https://github.com/Shuttle/Shuttle.Access</projectUrl>
15+
<description>Authorization middleware for web API endpoints using Shuttle.Access.</description>
16+
<copyright>Copyright (c) 2025, Eben Roux</copyright>
17+
<tags>iam middleware authorization permissions</tags>
18+
<dependencies>
19+
<dependency id="Shuttle.Access" version="6.0.2" />
2020
<dependency id="Shuttle.Core.Contract" version="20.0.0" />
21-
</dependencies>
22-
</metadata>
23-
<files>
24-
<file src="..\..\..\.media\logo.png" target="images" />
25-
<file src="..\..\..\README.md" target="docs" />
26-
<file src="lib\**\*.*" target="lib" />
27-
</files>
21+
</dependencies>
22+
</metadata>
23+
<files>
24+
<file src="..\..\..\.media\logo.png" target="images" />
25+
<file src="..\..\..\README.md" target="docs" />
26+
<file src="lib\**\*.*" target="lib" />
27+
</files>
2828
</package>
Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
<?xml version="1.0"?>
22

33
<package>
4-
<metadata>
5-
<id>Shuttle.Access.AspNetCore</id>
6-
<version>#{SemanticVersion}#</version>
7-
<authors>Eben Roux</authors>
8-
<owners>Eben Roux</owners>
9-
<license type="expression">BSD-3-Clause</license>
10-
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11-
<icon>images\logo.png</icon>
12-
<readme>docs\README.md</readme>
13-
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14-
<projectUrl>https://github.com/Shuttle/Shuttle.Access</projectUrl>
15-
<description>Authorization middleware for web API endpoints using Shuttle.Access.</description>
16-
<copyright>Copyright (c) #{Year}#, Eben Roux</copyright>
17-
<tags>iam middleware authorization permissions</tags>
18-
<dependencies>
19-
#{Dependencies}#
20-
</dependencies>
21-
</metadata>
22-
<files>
23-
<file src="..\..\..\.media\logo.png" target="images" />
24-
<file src="..\..\..\README.md" target="docs" />
25-
<file src="lib\**\*.*" target="lib" />
26-
</files>
4+
<metadata>
5+
<id>Shuttle.Access.AspNetCore</id>
6+
<version>#{SemanticVersion}#</version>
7+
<authors>Eben Roux</authors>
8+
<owners>Eben Roux</owners>
9+
<license type="expression">BSD-3-Clause</license>
10+
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11+
<icon>images\logo.png</icon>
12+
<readme>docs\README.md</readme>
13+
<repository type="git" url="https://github.com/Shuttle/Shuttle.Access.git" />
14+
<projectUrl>https://github.com/Shuttle/Shuttle.Access</projectUrl>
15+
<description>Authorization middleware for web API endpoints using Shuttle.Access.</description>
16+
<copyright>Copyright (c) #{Year}#, Eben Roux</copyright>
17+
<tags>iam middleware authorization permissions</tags>
18+
<dependencies>
19+
#{Dependencies}#
20+
</dependencies>
21+
</metadata>
22+
<files>
23+
<file src="..\..\..\.media\logo.png" target="images" />
24+
<file src="..\..\..\README.md" target="docs" />
25+
<file src="lib\**\*.*" target="lib" />
26+
</files>
2727
</package>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Security.Claims;
2+
using System.Text.Encodings.Web;
3+
using System.Text.Json;
4+
using System.Text.RegularExpressions;
5+
using Microsoft.AspNetCore.Authentication;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.AspNetCore.WebUtilities;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Options;
11+
using Shuttle.Core.Contract;
12+
13+
namespace Shuttle.Access.AspNetCore;
14+
15+
public class AccessAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
16+
{
17+
private readonly IAccessService _accessService;
18+
public static readonly string AuthenticationScheme = "Shuttle.Access";
19+
public const string SessionTokenClaimType = "http://shuttle.org/claims/session/token";
20+
public static readonly Regex TokenExpression = new(@"token\s*=\s*(?<token>[0-9a-fA-F-]{36})", RegexOptions.IgnoreCase);
21+
private const string Type = "https://tools.ietf.org/html/rfc9110#section-15.5.2";
22+
23+
public AccessAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, IAccessService accessService) : base(options, logger, encoder)
24+
{
25+
_accessService = Guard.AgainstNull(accessService);
26+
}
27+
28+
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
29+
{
30+
await Task.CompletedTask;
31+
32+
var header = Request.Headers["Authorization"].FirstOrDefault();
33+
34+
if (header == null)
35+
{
36+
return AuthenticateResult.NoResult();
37+
}
38+
39+
if (!header.StartsWith("Shuttle.Access ", StringComparison.OrdinalIgnoreCase))
40+
{
41+
return AuthenticateResult.NoResult();
42+
}
43+
44+
var match = TokenExpression.Match(header["Shuttle.Access ".Length..].Trim());
45+
46+
if (!match.Success ||
47+
!Guid.TryParse(match.Groups["token"].Value, out var sessionToken))
48+
{
49+
return AuthenticateResult.Fail(Resources.InvalidAuthenticationHeader);
50+
}
51+
52+
var session = await _accessService.FindSessionAsync(sessionToken);
53+
54+
if (session == null)
55+
{
56+
return AuthenticateResult.Fail(Resources.InvalidAuthenticationHeader);
57+
}
58+
59+
Context.SetPrincipalAccessSessionToken(sessionToken);
60+
61+
List<Claim> claims =
62+
[
63+
new(ClaimTypes.NameIdentifier, session.IdentityName),
64+
new(ClaimTypes.Name, session.IdentityName),
65+
new(nameof(session.IdentityName), session.IdentityName),
66+
new(SessionTokenClaimType, $"{session.Token:D}")
67+
];
68+
69+
return AuthenticateResult.Success(new(new(new ClaimsIdentity(claims, Scheme.Name)), Scheme.Name));
70+
}
71+
72+
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
73+
{
74+
var authenticateResult = await HandleAuthenticateOnceAsync();
75+
76+
if (authenticateResult.Succeeded)
77+
{
78+
return;
79+
}
80+
81+
Response.StatusCode = StatusCodes.Status401Unauthorized;
82+
Response.Headers.WWWAuthenticate = "Shuttle.Access";
83+
84+
var problemDetails = new ProblemDetails
85+
{
86+
Type = Type,
87+
Title = ReasonPhrases.GetReasonPhrase(StatusCodes.Status401Unauthorized),
88+
Status = StatusCodes.Status401Unauthorized,
89+
Detail = authenticateResult.Failure?.Message
90+
};
91+
92+
await Response.WriteAsJsonAsync(problemDetails, (JsonSerializerOptions?)null, "application/problem+json");
93+
}
94+
}

Shuttle.Access.AspNetCore/AccessAuthorizationMiddleware.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Security.Claims;
2-
using System.Text.RegularExpressions;
32
using Microsoft.AspNetCore.Http;
43
using Microsoft.Extensions.Options;
54
using Microsoft.Extensions.Primitives;

0 commit comments

Comments
 (0)