Skip to content

Commit 52b6af4

Browse files
authored
Merge pull request #21 from jokk-itu/feature/step-up-authentication
Feature/step up authentication
2 parents 8f4536d + fb64754 commit 52b6af4

16 files changed

+197
-46
lines changed

src/AuthServer/Core/Parameter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public static class Parameter
8989
public const string GrantManagementAction = "grant_management_action";
9090
public const string Scopes = "scopes";
9191
public const string Claims = "claims";
92+
public const string AuthTime = "auth_time";
93+
public const string Acr = "acr";
9294

9395
// Custom parameter
9496
public const string RequireReferenceToken = "require_reference_token";

src/AuthServer/Endpoints/Responses/PostIntrospectionResponse.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,10 @@ internal class PostIntrospectionResponse
3939

4040
[JsonPropertyName(Parameter.JwtId)]
4141
public string? JwtId { get; init; }
42+
43+
[JsonPropertyName(Parameter.AuthTime)]
44+
public long? AuthTime { get; init; }
45+
46+
[JsonPropertyName(Parameter.Acr)]
47+
public string? Acr { get; init; }
4248
}

src/AuthServer/Extensions/DateTimeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ public static long ToUnixTimeSeconds(this DateTime dateTime)
55
{
66
return (long)dateTime.Subtract(DateTime.UnixEpoch).TotalSeconds;
77
}
8-
}
8+
}

src/AuthServer/Introspection/IntrospectionEndpointHandler.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ public async Task<IResult> Handle(HttpContext httpContext, CancellationToken can
3636
JwtId = response.JwtId,
3737
NotBefore = response.NotBefore,
3838
Scope = response.Scope,
39-
Subject = response.Subject
39+
Subject = response.Subject,
40+
AuthTime = response.AuthTime,
41+
Acr = response.Acr
4042
}),
4143
error => Results.Extensions.OAuthBadRequest(error));
4244
}

src/AuthServer/Introspection/IntrospectionRequestProcessor.cs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using AuthServer.Authentication.Abstractions;
22
using AuthServer.Core;
33
using AuthServer.Core.Abstractions;
4-
using AuthServer.Core.Request;
54
using AuthServer.Entities;
65
using AuthServer.Extensions;
76
using AuthServer.Metrics;
@@ -33,24 +32,27 @@ public async Task<IntrospectionResponse> Process(IntrospectionValidatedRequest r
3332
.Select(x => new TokenQuery
3433
{
3534
Token = x,
35+
ClientIdFromClientAccessToken = (x as ClientAccessToken)!.Client.Id,
36+
ClientIdFromGrantAccessToken = (x as GrantAccessToken)!.AuthorizationGrant.Client.Id,
3637
SubjectFromGrantToken = (x as GrantToken)!.AuthorizationGrant.Subject,
3738
SubjectFromClientToken = (x as ClientAccessToken)!.Client.Id,
38-
SubjectIdentifier = (x as GrantToken)!.AuthorizationGrant.Session.SubjectIdentifier.Id
39+
SubjectIdentifier = (x as GrantToken)!.AuthorizationGrant.Session.SubjectIdentifier.Id,
40+
AuthTime = (x as GrantToken)!.AuthorizationGrant.AuthTime,
41+
Acr = (x as GrantAccessToken)!.AuthorizationGrant.AuthenticationContextReference.Name
3942
})
4043
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
4144

4245
var isInvalidToken = query is null;
4346
var hasExceededExpiration = query?.Token.ExpiresAt < DateTime.UtcNow;
4447
var isRevoked = query?.Token.RevokedAt is not null;
45-
4648
var scope = query?.Token.Scope?.Split(' ') ?? [];
47-
var hasInsufficientScope = !request.Scope.Intersect(scope).Any();
49+
var authorizedScope = request.Scope.Intersect(scope).ToList();
4850

4951
/*
5052
* If active is false, then the requesting client does not need to know more.
5153
* Therefore, the other optional properties are not set.
5254
*/
53-
if (isInvalidToken || hasExceededExpiration || isRevoked || hasInsufficientScope)
55+
if (isInvalidToken || hasExceededExpiration || isRevoked || authorizedScope.Count == 0)
5456
{
5557
return new IntrospectionResponse
5658
{
@@ -67,30 +69,38 @@ public async Task<IntrospectionResponse> Process(IntrospectionValidatedRequest r
6769

6870
var subject = query.SubjectFromGrantToken ?? query.SubjectFromClientToken;
6971

70-
_metricService.AddIntrospectedToken(query.Token is RefreshToken ? TokenTypeTag.RefreshToken : TokenTypeTag.AccessToken);
72+
_metricService.AddIntrospectedToken(query.Token is RefreshToken
73+
? TokenTypeTag.RefreshToken
74+
: TokenTypeTag.AccessToken);
7175

7276
return new IntrospectionResponse
7377
{
7478
Active = token.RevokedAt is null,
7579
JwtId = token.Id.ToString(),
76-
ClientId = request.ClientId,
80+
ClientId = query.ClientIdFromClientAccessToken ?? query.ClientIdFromGrantAccessToken,
7781
ExpiresAt = token.ExpiresAt?.ToUnixTimeSeconds(),
7882
Issuer = token.Issuer,
7983
Audience = token.Audience.Split(' '),
8084
IssuedAt = token.IssuedAt.ToUnixTimeSeconds(),
8185
NotBefore = token.NotBefore.ToUnixTimeSeconds(),
82-
Scope = token.Scope,
86+
Scope = string.Join(' ', authorizedScope),
8387
Subject = subject,
8488
TokenType = token.TokenType.GetDescription(),
85-
Username = username
89+
Username = username,
90+
AuthTime = query.AuthTime?.ToUnixTimeSeconds(),
91+
Acr = query.Acr
8692
};
8793
}
8894

8995
private sealed class TokenQuery
9096
{
9197
public required Token Token { get; init; }
92-
public required string? SubjectFromGrantToken { get; init; }
93-
public required string? SubjectFromClientToken { get; init; }
94-
public required string? SubjectIdentifier { get; init; }
98+
public string? ClientIdFromClientAccessToken { get; init; }
99+
public string? ClientIdFromGrantAccessToken { get; init; }
100+
public string? SubjectFromGrantToken { get; init; }
101+
public string? SubjectFromClientToken { get; init; }
102+
public string? SubjectIdentifier { get; init; }
103+
public DateTime? AuthTime { get; init; }
104+
public string? Acr { get; init; }
95105
}
96106
}

src/AuthServer/Introspection/IntrospectionRequestValidator.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using AuthServer.Authentication;
2-
using AuthServer.Authentication.Abstractions;
1+
using AuthServer.Authentication.Abstractions;
32
using AuthServer.Cache.Abstractions;
43
using AuthServer.Constants;
54
using AuthServer.Core.Abstractions;
@@ -63,7 +62,6 @@ public async Task<ProcessResult<IntrospectionValidatedRequest, ProcessError>> Va
6362

6463
return new IntrospectionValidatedRequest
6564
{
66-
ClientId = clientAuthenticationResult.ClientId,
6765
Token = request.Token!,
6866
Scope = cachedClient.Scopes
6967
};

src/AuthServer/Introspection/IntrospectionResponse.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ internal class IntrospectionResponse
1313
public IEnumerable<string> Audience { get; init; } = [];
1414
public string? Issuer { get; init; }
1515
public string? JwtId { get; init; }
16+
public long? AuthTime { get; init; }
17+
public string? Acr { get; init; }
1618
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace AuthServer.Introspection;
22
internal class IntrospectionValidatedRequest
33
{
4-
public required string ClientId { get; init; }
54
public required string Token { get; init; }
65
public required IReadOnlyCollection<string> Scope { get; init; }
76
}

src/AuthServer/TokenBuilders/GrantAccessTokenBuilder.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public async Task<string> BuildToken(GrantAccessTokenArguments arguments, Cancel
4545
AuthorizationGrant = x,
4646
Client = x.Client,
4747
Subject = x.Subject,
48-
SessionId = x.Session.Id
48+
SessionId = x.Session.Id,
49+
Acr = x.AuthenticationContextReference.Name
4950
})
5051
.SingleAsync(cancellationToken);
5152

@@ -73,7 +74,9 @@ private string BuildStructuredToken(GrantAccessTokenArguments arguments, GrantQu
7374
{ ClaimNameConstants.GrantId, arguments.AuthorizationGrantId },
7475
{ ClaimNameConstants.Sub, grantQuery.Subject },
7576
{ ClaimNameConstants.Sid, grantQuery.SessionId },
76-
{ ClaimNameConstants.ClientId, grantQuery.Client.Id }
77+
{ ClaimNameConstants.ClientId, grantQuery.Client.Id },
78+
{ ClaimNameConstants.AuthTime, grantQuery.AuthorizationGrant.AuthTime.ToUnixTimeSeconds() },
79+
{ ClaimNameConstants.Acr, grantQuery.Acr }
7780
};
7881

7982
var now = DateTime.UtcNow;
@@ -110,5 +113,6 @@ private sealed class GrantQuery
110113
public required Client Client { get; init; }
111114
public required string SessionId { get; init; }
112115
public required string Subject { get; init; }
116+
public required string Acr { get; init; }
113117
}
114118
}

tests/AuthServer.Tests.IntegrationTest/BaseIntegrationTest.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using AuthServer.Core;
55
using AuthServer.Entities;
66
using AuthServer.Enums;
7+
using AuthServer.Helpers;
78
using AuthServer.Options;
89
using AuthServer.Repositories.Abstractions;
910
using AuthServer.Tests.Core;
@@ -175,7 +176,7 @@ protected async Task<string> AddWeatherReadScope()
175176
return scopeName;
176177
}
177178

178-
protected async Task<Client> AddWeatherClient()
179+
protected async Task<Client> AddWeatherClient(string plainSecret)
179180
{
180181
var dbContext = ServiceProvider.GetRequiredService<AuthorizationDbContext>();
181182

@@ -186,6 +187,8 @@ protected async Task<Client> AddWeatherClient()
186187
ClientUri = "https://weather.authserver.dk"
187188
};
188189

190+
client.SetSecret(CryptographyHelper.HashPassword(plainSecret));
191+
189192
await dbContext.AddAsync(client);
190193
await dbContext.SaveChangesAsync();
191194

0 commit comments

Comments
 (0)