Skip to content
This repository was archived by the owner on Dec 24, 2020. It is now read-only.

Commit 6442dab

Browse files
committed
Introduce SaveToken to enable delegation scenarios and copy scopes to user claims
1 parent ea9e7ae commit 6442dab

File tree

17 files changed

+508
-6
lines changed

17 files changed

+508
-6
lines changed

src/AspNet.Security.OAuth.Introspection/OAuthIntrospectionConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public static class Parameters {
2929

3030
public static class Properties {
3131
public const string Audiences = ".audiences";
32+
public const string Scopes = ".scopes";
33+
public const string Token = "access_token";
3234
}
3335

3436
public static class TokenTypes {

src/AspNet.Security.OAuth.Introspection/OAuthIntrospectionHandler.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
8787

8888
// Create a new authentication ticket from the introspection
8989
// response returned by the authorization server.
90-
ticket = await CreateTicketAsync(payload);
90+
ticket = await CreateTicketAsync(token, payload);
9191
Debug.Assert(ticket != null);
9292

9393
await StoreTicketAsync(token, ticket);
@@ -228,10 +228,17 @@ protected virtual bool ValidateAudience(AuthenticationTicket ticket) {
228228
return true;
229229
}
230230

231-
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(JObject payload) {
231+
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(string token, JObject payload) {
232232
var identity = new ClaimsIdentity(Options.AuthenticationScheme);
233233
var properties = new AuthenticationProperties();
234234

235+
if (Options.SaveToken) {
236+
// Store the access token in the authentication ticket.
237+
properties.StoreTokens(new[] {
238+
new AuthenticationToken { Name = OAuthIntrospectionConstants.Properties.Token, Value = token }
239+
});
240+
}
241+
235242
foreach (var property in payload.Properties()) {
236243
switch (property.Name) {
237244
// Ignore the unwanted claims.
@@ -283,14 +290,21 @@ protected virtual async Task<AuthenticationTicket> CreateTicketAsync(JObject pay
283290
// "scope" claim and store them as individual claims.
284291
// See https://tools.ietf.org/html/rfc7662#section-2.2
285292
case OAuthIntrospectionConstants.Claims.Scope: {
286-
foreach (var scope in property.Value.ToObject<string>().Split(' ')) {
293+
var scopes = (string) property.Value;
294+
295+
// Store the scopes list as-is in the authentication properties.
296+
properties.Items[OAuthIntrospectionConstants.Properties.Scopes] = scopes;
297+
298+
foreach (var scope in scopes.Split(' ')) {
287299
identity.AddClaim(new Claim(property.Name, scope));
288300
}
289301

290302
continue;
291303
}
292304

293305
// Store the audience(s) in the ticket properties.
306+
// Note: the "aud" claim may be either a list of strings or a unique string.
307+
// See https://tools.ietf.org/html/rfc7662#section-2.2
294308
case OAuthIntrospectionConstants.Claims.Audience: {
295309
if (property.Value.Type == JTokenType.Array) {
296310
var value = (JArray) property.Value;

src/AspNet.Security.OAuth.Introspection/OAuthIntrospectionOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Authentication;
1010
using Microsoft.AspNetCore.Builder;
1111
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.AspNetCore.Http.Authentication;
1213
using Microsoft.Extensions.Caching.Distributed;
1314

1415
namespace AspNet.Security.OAuth.Introspection {
@@ -47,6 +48,12 @@ public OAuthIntrospectionOptions() {
4748
/// </summary>
4849
public string ClientSecret { get; set; }
4950

51+
/// <summary>
52+
/// Gets or sets a boolean determining whether the access token should be stored in the
53+
/// <see cref="AuthenticationProperties"/> after a successful authentication process.
54+
/// </summary>
55+
public bool SaveToken { get; set; } = true;
56+
5057
/// <summary>
5158
/// Gets or sets the cache used to store the authentication tickets
5259
/// resolved from the access tokens received by the resource server.

src/AspNet.Security.OAuth.Validation/OAuthValidationConstants.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66

77
namespace AspNet.Security.OAuth.Validation {
88
public static class OAuthValidationConstants {
9+
public static class Claims {
10+
public const string Scope = "scope";
11+
}
12+
913
public static class Properties {
1014
public const string Audiences = ".audiences";
15+
public const string Scopes = ".scopes";
16+
public const string Token = "access_token";
1117
}
1218
}
1319
}

src/AspNet.Security.OAuth.Validation/OAuthValidationHandler.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
*/
66

77
using System;
8+
using System.Diagnostics;
89
using System.Linq;
10+
using System.Security.Claims;
911
using System.Threading.Tasks;
1012
using Microsoft.AspNetCore.Authentication;
1113
using Microsoft.Extensions.Logging;
@@ -137,6 +139,28 @@ protected virtual bool ValidateAudience(AuthenticationTicket ticket) {
137139

138140
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(string token) {
139141
var ticket = Options.AccessTokenFormat.Unprotect(token);
142+
if (ticket == null) {
143+
return null;
144+
}
145+
146+
if (Options.SaveToken) {
147+
// Store the access token in the authentication ticket.
148+
ticket.Properties.StoreTokens(new[] {
149+
new AuthenticationToken { Name = OAuthValidationConstants.Properties.Token, Value = token }
150+
});
151+
}
152+
153+
var identity = ticket.Principal.Identity as ClaimsIdentity;
154+
Debug.Assert(identity != null);
155+
156+
string scopes;
157+
// Copy the scopes extracted from the authentication ticket to the
158+
// ClaimsIdentity to make them easier to retrieve from application code.
159+
if (ticket.Properties.Items.TryGetValue(OAuthValidationConstants.Properties.Scopes, out scopes)) {
160+
foreach (var scope in scopes.Split(' ')) {
161+
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Scope, scope));
162+
}
163+
}
140164

141165
var notification = new CreateTicketContext(Context, Options, ticket);
142166
await Options.Events.CreateTicket(notification);

src/AspNet.Security.OAuth.Validation/OAuthValidationOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Authentication;
99
using Microsoft.AspNetCore.Builder;
1010
using Microsoft.AspNetCore.DataProtection;
11+
using Microsoft.AspNetCore.Http.Authentication;
1112

1213
namespace AspNet.Security.OAuth.Validation {
1314
public class OAuthValidationOptions : AuthenticationOptions {
@@ -24,6 +25,12 @@ public OAuthValidationOptions() {
2425
/// </summary>
2526
public IList<string> Audiences { get; } = new List<string>();
2627

28+
/// <summary>
29+
/// Gets or sets a boolean determining whether the access token should be stored in the
30+
/// <see cref="AuthenticationProperties"/> after a successful authentication process.
31+
/// </summary>
32+
public bool SaveToken { get; set; } = true;
33+
2734
/// <summary>
2835
/// Gets or sets the clock used to determine the current date/time.
2936
/// </summary>

src/Owin.Security.OAuth.Introspection/OAuthIntrospectionConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public static class Parameters {
3333

3434
public static class Properties {
3535
public const string Audiences = ".audiences";
36+
public const string Scopes = ".scopes";
37+
public const string Token = "access_token";
3638
}
3739

3840
public static class TokenTypes {

src/Owin.Security.OAuth.Introspection/OAuthIntrospectionHandler.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() {
9090

9191
// Create a new authentication ticket from the introspection
9292
// response returned by the authorization server.
93-
ticket = await CreateTicketAsync(payload);
93+
ticket = await CreateTicketAsync(token, payload);
9494
Debug.Assert(ticket != null);
9595

9696
await StoreTicketAsync(token, ticket);
@@ -231,10 +231,15 @@ protected virtual bool ValidateAudience(AuthenticationTicket ticket) {
231231
return true;
232232
}
233233

234-
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(JObject payload) {
234+
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(string token, JObject payload) {
235235
var identity = new ClaimsIdentity(Options.AuthenticationType);
236236
var properties = new AuthenticationProperties();
237237

238+
if (Options.SaveToken) {
239+
// Store the access token in the authentication ticket.
240+
properties.Dictionary[OAuthIntrospectionConstants.Properties.Token] = token;
241+
}
242+
238243
foreach (var property in payload.Properties()) {
239244
switch (property.Name) {
240245
// Ignore the unwanted claims.
@@ -275,14 +280,21 @@ protected virtual async Task<AuthenticationTicket> CreateTicketAsync(JObject pay
275280
// "scope" claim and store them as individual claims.
276281
// See https://tools.ietf.org/html/rfc7662#section-2.2
277282
case OAuthIntrospectionConstants.Claims.Scope: {
278-
foreach (var scope in property.Value.ToObject<string>().Split(' ')) {
283+
var scopes = (string) property.Value;
284+
285+
// Store the scopes list as-is in the authentication properties.
286+
properties.Dictionary[OAuthIntrospectionConstants.Properties.Scopes] = scopes;
287+
288+
foreach (var scope in scopes.Split(' ')) {
279289
identity.AddClaim(new Claim(property.Name, scope));
280290
}
281291

282292
continue;
283293
}
284294

285295
// Store the audience(s) in the ticket properties.
296+
// Note: the "aud" claim may be either a list of strings or a unique string.
297+
// See https://tools.ietf.org/html/rfc7662#section-2.2
286298
case OAuthIntrospectionConstants.Claims.Audience: {
287299
if (property.Value.Type == JTokenType.Array) {
288300
var value = (JArray) property.Value;

src/Owin.Security.OAuth.Introspection/OAuthIntrospectionOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ public OAuthIntrospectionOptions()
4747
/// </summary>
4848
public string ClientSecret { get; set; }
4949

50+
/// <summary>
51+
/// Gets or sets a boolean determining whether the access token should be stored in the
52+
/// <see cref="AuthenticationProperties"/> after a successful authentication process.
53+
/// </summary>
54+
public bool SaveToken { get; set; } = true;
55+
5056
/// <summary>
5157
/// Gets or sets the cache used to store the authentication tickets
5258
/// resolved from the access tokens received by the resource server.

src/Owin.Security.OAuth.Validation/OAuthValidationConstants.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@
66

77
namespace Owin.Security.OAuth.Validation {
88
public static class OAuthValidationConstants {
9+
public static class Claims {
10+
public const string Scope = "scope";
11+
}
12+
913
public static class Headers {
1014
public const string Authorization = "Authorization";
1115
}
1216

1317
public static class Properties {
1418
public const string Audiences = ".audiences";
19+
public const string Scopes = ".scopes";
20+
public const string Token = "access_token";
1521
}
1622
}
1723
}

0 commit comments

Comments
 (0)