Skip to content

Commit 96e62e8

Browse files
authored
Merge pull request #23 from Cabazure/request-scopes
Add ability to set auth scopes via request options
2 parents 8ee7a2e + eebede4 commit 96e62e8

File tree

12 files changed

+65
-41
lines changed

12 files changed

+65
-41
lines changed

samples/AzureRest/AzureRest.Client/AzureRestClientOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ Uri ICabazureClientOptions.GetBaseAddress()
1313
// for the client to work correctly with relative paths.
1414
=> new("https://management.azure.com/");
1515

16-
string ICabazureAuthClientOptions.GetScope()
16+
string[] ICabazureAuthClientOptions.GetScopes()
1717
// The default scope for Microsoft Graph.
18-
=> "https://management.azure.com/.default";
18+
=> new string[] { "https://management.azure.com/.default" };
1919

2020
TokenCredential ICabazureAuthClientOptions.GetCredential()
2121
=> Credential

src/Cabazure.Client.Runtime/Authentication/AzureAuthenticationHandler.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
public class AzureAuthenticationHandler
44
: DelegatingHandler
55
{
6+
private readonly string[] defaultScopes;
67
private readonly IBearerTokenProvider tokenProvider;
78

89
public AzureAuthenticationHandler(
10+
string[] defaultScopes,
911
IBearerTokenProvider tokenProvider)
1012
{
11-
this.tokenProvider = tokenProvider;
13+
this.defaultScopes = defaultScopes;
14+
this.tokenProvider = tokenProvider;
1215
}
1316

1417
protected async override Task<HttpResponseMessage> SendAsync(
1518
HttpRequestMessage request,
1619
CancellationToken cancellationToken)
1720
{
18-
request.Headers.Authorization = await tokenProvider.GetTokenAsync(cancellationToken);
21+
var scopes = request.GetScopes() ?? defaultScopes;
22+
23+
request.Headers.Authorization = await tokenProvider.GetTokenAsync(scopes, cancellationToken);
1924

2025
return await base.SendAsync(request, cancellationToken);
2126
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
1-
using System.Net.Http.Headers;
1+
using System.Collections.Concurrent;
2+
using System.Net.Http.Headers;
23
using Azure.Core;
34

45
namespace Cabazure.Client.Authentication
56
{
67
public class BearerTokenProvider : IBearerTokenProvider
78
{
8-
private readonly TokenRequestContext context;
99
private readonly TokenCredential credential;
1010
private readonly IDateTimeProvider dateTimeProvider;
11-
private AccessToken accessToken;
11+
private readonly ConcurrentDictionary<string, AccessToken> accessTokenCache = new();
1212

1313
public BearerTokenProvider(
14-
TokenRequestContext context,
1514
TokenCredential credential,
1615
IDateTimeProvider dateTimeProvider)
1716
{
18-
this.context = context;
1917
this.credential = credential;
2018
this.dateTimeProvider = dateTimeProvider;
2119
}
2220

2321
public async Task<AuthenticationHeaderValue> GetTokenAsync(
22+
string[] scopes,
2423
CancellationToken cancellationToken)
2524
{
26-
if (TokenIsExpired())
25+
var key = string.Join(" ", scopes);
26+
if (!accessTokenCache.TryGetValue(key, out var accessToken) || TokenIsExpired(accessToken))
2727
{
28-
accessToken = await credential.GetTokenAsync(context, cancellationToken);
28+
accessTokenCache[key] = accessToken = await credential.GetTokenAsync(new TokenRequestContext(scopes), cancellationToken);
2929
}
3030

3131
return new AuthenticationHeaderValue(
3232
"Bearer",
3333
accessToken.Token);
3434
}
3535

36-
private bool TokenIsExpired()
36+
private bool TokenIsExpired(AccessToken accessToken)
3737
=> dateTimeProvider.GetDateTime().AddMinutes(1) > accessToken.ExpiresOn;
3838
}
3939
}

src/Cabazure.Client.Runtime/Authentication/IBearerTokenProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Cabazure.Client.Authentication
55
public interface IBearerTokenProvider
66
{
77
Task<AuthenticationHeaderValue> GetTokenAsync(
8+
string[] scopes,
89
CancellationToken cancellationToken);
910
}
1011
}

src/Cabazure.Client.Runtime/Builder/HttpClientBuilderExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,21 @@ public static IHttpClientBuilder AddAuthentication(
1616
TokenCredential credential)
1717
=> AddAuthentication(
1818
builder,
19-
new TokenRequestContext(new[] { scope }),
19+
new[] { scope },
2020
credential);
2121

2222
public static IHttpClientBuilder AddAuthentication(
2323
this IHttpClientBuilder builder,
24-
TokenRequestContext context,
24+
string[] scopes,
2525
TokenCredential credential)
2626
{
2727
var tokenProvider = new BearerTokenProvider(
28-
context,
2928
credential,
3029
new DateTimeProvider());
3130

3231
return builder
3332
.AddHttpMessageHandler(
34-
() => new AzureAuthenticationHandler(tokenProvider));
33+
() => new AzureAuthenticationHandler(scopes, tokenProvider));
3534
}
3635
}
3736
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Cabazure.Client;
2+
3+
public static class HttpRequestMessageExtensions
4+
{
5+
private const string ScopesKey = "scopes";
6+
7+
public static void SetScopes(
8+
this HttpRequestMessage request,
9+
params string[] scopes)
10+
=> request.Properties[ScopesKey] = scopes;
11+
12+
public static string[]? GetScopes(this HttpRequestMessage request)
13+
{
14+
if (request.Properties.TryGetValue(ScopesKey, out var value) && value is string[] scopes)
15+
{
16+
return scopes;
17+
}
18+
19+
return null;
20+
}
21+
}

src/Cabazure.Client.Runtime/ICabazureAuthClientOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ namespace Cabazure.Client
88
public interface ICabazureAuthClientOptions : ICabazureClientOptions
99
{
1010
/// <summary>
11-
/// The scope to use for authorization token.
11+
/// The scopes to use for authorization token.
1212
/// </summary>
13-
/// <returns>The authorization scope.</returns>
14-
string GetScope();
13+
/// <returns>The authorization scopes.</returns>
14+
string[] GetScopes();
1515

1616
/// <summary>
1717
/// The credential to use for authentication.

src/Cabazure.Client/ClientInitializationGenerator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,14 @@ void ConfigureAuthHandler(IList<DelegatingHandler> handlers, IServiceProvider se
135135
136136
if (options is ICabazureAuthClientOptions authOptions)
137137
{
138-
var scope = authOptions.GetScope();
138+
var scopes = authOptions.GetScopes();
139139
var credential = authOptions.GetCredential();
140140
141141
var tokenProvider = new BearerTokenProvider(
142-
new TokenRequestContext(new [] { scope }),
143142
credential,
144143
new DateTimeProvider());
145144
146-
handlers.Add(new AzureAuthenticationHandler(tokenProvider));
145+
handlers.Add(new AzureAuthenticationHandler(scopes, tokenProvider));
147146
}
148147
}
149148

test/Cabazure.Client.Runtime.Tests/Authentication/AzureAuthenticationHandlerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal async Task Should_Get_Token_On_Send(
1717
CancellationToken cancellationToken)
1818
{
1919
tokenProvider
20-
.GetTokenAsync(default)
20+
.GetTokenAsync(default, default)
2121
.ReturnsForAnyArgs(authenticationHeader);
2222
handler
2323
.InvokeProtectedMethod<Task<HttpResponseMessage>>("SendAsync", request, cancellationToken)
@@ -29,6 +29,6 @@ internal async Task Should_Get_Token_On_Send(
2929

3030
_ = tokenProvider
3131
.Received(1)
32-
.GetTokenAsync(Arg.Any<CancellationToken>());
32+
.GetTokenAsync(Arg.Any<string[]>(), Arg.Any<CancellationToken>());
3333
}
3434
}

test/Cabazure.Client.Runtime.Tests/Authentication/BearerTokenProviderTests.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public class BearerTokenProviderTests
99
internal async Task Should_Return_Token_From_TokenCredential(
1010
[Frozen] TokenCredential credential,
1111
[Frozen] IDateTimeProvider dateTimeProvider,
12-
[Frozen] TokenRequestContext context,
1312
BearerTokenProvider sut,
13+
string[] scopes,
1414
DateTimeOffset timestamp,
1515
AccessToken accessToken,
1616
CancellationToken cancellationToken)
@@ -23,7 +23,7 @@ internal async Task Should_Return_Token_From_TokenCredential(
2323
.GetTokenAsync(default, default)
2424
.ReturnsForAnyArgs(accessToken);
2525

26-
var response = await sut.GetTokenAsync(cancellationToken);
26+
var response = await sut.GetTokenAsync(scopes, cancellationToken);
2727

2828
response
2929
.Parameter
@@ -35,8 +35,8 @@ internal async Task Should_Return_Token_From_TokenCredential(
3535
internal async Task Should_Use_Cached_Token_From_TokenCredential(
3636
[Frozen] TokenCredential credential,
3737
[Frozen] IDateTimeProvider dateTimeProvider,
38-
[Frozen] TokenRequestContext context,
3938
BearerTokenProvider sut,
39+
string[] scopes,
4040
DateTimeOffset timestamp,
4141
CancellationToken cancellationToken)
4242
{
@@ -52,8 +52,8 @@ internal async Task Should_Use_Cached_Token_From_TokenCredential(
5252
.GetTokenAsync(default, default)
5353
.ReturnsForAnyArgs(accessToken);
5454

55-
var response1 = await sut.GetTokenAsync(cancellationToken);
56-
var response2 = await sut.GetTokenAsync(cancellationToken);
55+
var response1 = await sut.GetTokenAsync(scopes, cancellationToken);
56+
var response2 = await sut.GetTokenAsync(scopes, cancellationToken);
5757

5858
response1
5959
.Should()
@@ -70,8 +70,8 @@ internal async Task Should_Use_Cached_Token_From_TokenCredential(
7070
internal async Task Should_Renew_Expired_Token(
7171
[Frozen] TokenCredential credential,
7272
[Frozen] IDateTimeProvider dateTimeProvider,
73-
[Frozen] TokenRequestContext context,
7473
BearerTokenProvider sut,
74+
string[] scopes,
7575
DateTimeOffset timestamp,
7676
CancellationToken cancellationToken)
7777
{
@@ -87,9 +87,9 @@ internal async Task Should_Renew_Expired_Token(
8787
.GetTokenAsync(default, default)
8888
.ReturnsForAnyArgs(accessToken);
8989

90-
await sut.GetTokenAsync(cancellationToken);
91-
await sut.GetTokenAsync(cancellationToken);
92-
await sut.GetTokenAsync(cancellationToken);
90+
await sut.GetTokenAsync(scopes, cancellationToken);
91+
await sut.GetTokenAsync(scopes, cancellationToken);
92+
await sut.GetTokenAsync(scopes, cancellationToken);
9393

9494
_ = credential
9595
.Received(3)
@@ -102,8 +102,8 @@ internal async Task Should_Renew_Expired_Token(
102102
internal async Task Should_Use_Bearer_AuthorizationToken(
103103
[Frozen] TokenCredential credential,
104104
[Frozen] IDateTimeProvider dateTimeProvider,
105-
[Frozen] TokenRequestContext context,
106105
BearerTokenProvider sut,
106+
string[] scopes,
107107
DateTimeOffset timestamp,
108108
CancellationToken cancellationToken)
109109
{
@@ -119,7 +119,7 @@ internal async Task Should_Use_Bearer_AuthorizationToken(
119119
.GetTokenAsync(default, default)
120120
.ReturnsForAnyArgs(accessToken);
121121

122-
var response = await sut.GetTokenAsync(cancellationToken);
122+
var response = await sut.GetTokenAsync(scopes, cancellationToken);
123123

124124
response
125125
.Scheme

0 commit comments

Comments
 (0)