-
Notifications
You must be signed in to change notification settings - Fork 186
/
InteractiveAuthenticationProvider.cs
194 lines (173 loc) · 8.05 KB
/
InteractiveAuthenticationProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Client;
using PnP.Core.Auth.Services.Builder.Configuration;
using PnP.Core.Auth.Utilities;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace PnP.Core.Auth
{
/// <summary>
/// Authentication Provider that uses an interactive flow prompting the user for credentials
/// </summary>
public class InteractiveAuthenticationProvider : OAuthAuthenticationProvider
{
/// <summary>
/// The Redirect URI for the authentication flow
/// </summary>
public Uri RedirectUri { get; set; }
// Instance private member, to keep the token cache at service instance level
private IPublicClientApplication publicClientApplication;
// Instance private member, to keep the Msal Http Client Factory at service instance level
private readonly IMsalHttpClientFactory msalHttpClientFactory;
/// <summary>
/// Public constructor for external consumers of the library
/// </summary>
/// <param name="clientId">The Client ID for the Authentication Provider</param>
/// <param name="tenantId">The Tenant ID for the Authentication Provider</param>
/// <param name="redirectUri">The Redirect URI for the authentication flow</param>
public InteractiveAuthenticationProvider(string clientId, string tenantId, Uri redirectUri)
: this(clientId, tenantId, new PnPCoreAuthenticationInteractiveOptions
{
RedirectUri = redirectUri
})
{
}
/// <summary>
/// Public constructor for external consumers of the library
/// </summary>
/// <param name="clientId">The Client ID for the Authentication Provider</param>
/// <param name="tenantId">The Tenant ID for the Authentication Provider</param>
/// <param name="options">Options for the authentication provider</param>
public InteractiveAuthenticationProvider(string clientId, string tenantId, PnPCoreAuthenticationInteractiveOptions options)
: this(null, null)
{
Init(new PnPCoreAuthenticationCredentialConfigurationOptions
{
ClientId = clientId,
TenantId = tenantId,
Interactive = options
});
}
/// <summary>
/// Public constructor for external consumers of the library
/// </summary>
public InteractiveAuthenticationProvider()
: this(null, null)
{
Init(new PnPCoreAuthenticationCredentialConfigurationOptions
{
ClientId = "",
TenantId = "",
Interactive = new PnPCoreAuthenticationInteractiveOptions()
});
}
/// <summary>
/// Public constructor leveraging DI to initialize the ILogger and IMsalHttpClientFactory interfaces
/// </summary>
/// <param name="logger">The instance of the logger service provided by DI</param>
/// <param name="msalHttpClientFactory">The instance of the Msal Http Client Factory service provided by DI</param>
public InteractiveAuthenticationProvider(ILogger<OAuthAuthenticationProvider> logger, IMsalHttpClientFactory msalHttpClientFactory)
: base(logger)
{
this.msalHttpClientFactory = msalHttpClientFactory;
}
/// <summary>
/// Initializes the Authentication Provider
/// </summary>
/// <param name="options">The options to use</param>
internal override void Init(PnPCoreAuthenticationCredentialConfigurationOptions options)
{
ClientId = !string.IsNullOrEmpty(options.ClientId) ? options.ClientId : AuthGlobals.DefaultClientId;
TenantId = !string.IsNullOrEmpty(options.TenantId) ? options.TenantId : AuthGlobals.OrganizationsTenantId;
RedirectUri = options.Interactive?.RedirectUri ?? AuthGlobals.DefaultRedirectUri;
// Build the MSAL client
publicClientApplication = PublicClientApplicationBuilder
.Create(ClientId)
.WithHttpClientFactory(msalHttpClientFactory)
.WithPnPAdditionalAuthenticationSettings(
options.Interactive?.AuthorityUri,
RedirectUri,
TenantId,
options.Environment,
options.AzureADLoginAuthority)
.Build();
// Log the initialization information
Log?.LogInformation(PnPCoreAuthResources.InteractiveAuthenticationProvider_LogInit);
}
/// <summary>
/// Authenticates the specified request message.
/// </summary>
/// <param name="resource">Request uri</param>
/// <param name="request">The <see cref="HttpRequestMessage"/> to authenticate.</param>
/// <returns>The task to await.</returns>
public override async Task AuthenticateRequestAsync(Uri resource, HttpRequestMessage request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (resource == null)
{
throw new ArgumentNullException(nameof(resource));
}
request.Headers.Authorization = new AuthenticationHeaderValue("bearer",
await GetAccessTokenAsync(resource).ConfigureAwait(false));
}
/// <summary>
/// Gets an access token for the requested resource and scope
/// </summary>
/// <param name="resource">Resource to request an access token for (unused)</param>
/// <param name="scopes">Scopes to request</param>
/// <returns>An access token</returns>
public override async Task<string> GetAccessTokenAsync(Uri resource, string[] scopes)
{
if (resource == null)
{
throw new ArgumentNullException(nameof(resource));
}
if (scopes == null)
{
throw new ArgumentNullException(nameof(scopes));
}
AuthenticationResult tokenResult = null;
var account = await publicClientApplication.GetAccountsAsync().ConfigureAwait(false);
try
{
// Try to get the token from the tokens cache
tokenResult = await publicClientApplication.AcquireTokenSilent(scopes, account.FirstOrDefault())
.ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
// Try to get the token directly through AAD if it is not available in the tokens cache
tokenResult = await publicClientApplication.AcquireTokenInteractive(scopes)
.ExecuteAsync().ConfigureAwait(false);
}
// Log the access token retrieval action
Log?.LogDebug(PnPCoreAuthResources.AuthenticationProvider_LogAccessTokenRetrieval,
GetType().Name, resource, scopes.Aggregate(string.Empty, (c, n) => c + ", " + n).TrimEnd(','));
// Return the Access Token, if we've got it
// In case of any exception while retrieving the access token,
// MSAL will throw an exception that we simply bubble up
return tokenResult.AccessToken;
}
/// <summary>
/// Gets an access token for the requested resource
/// </summary>
/// <param name="resource">Resource to request an access token for</param>
/// <returns>An access token</returns>
public override async Task<string> GetAccessTokenAsync(Uri resource)
{
if (resource == null)
{
throw new ArgumentNullException(nameof(resource));
}
// Use the .default scope if the scopes are not provided
return await GetAccessTokenAsync(resource,
new string[] { $"{resource.Scheme}://{resource.Authority}/.default" }).ConfigureAwait(false);
}
}
}