Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure ReplyUri for AuthorizationCodeCredential #21597

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Release History

## 1.5.0-beta.1 (Unreleased)
## 1.5.0-beta.1 (2021-06-08)

### Fixes and improvements

- Added `LoginHint` property to `InteractiveBrowserCredentialOptions` which allows a user name to be pre-selected for interactive logins. Setting this option skips the account selection prompt and immediately attempts to login with the specified account.
- Added `AuthorizationCodeCredentialOptions` which allows for configuration of a ReplyUri.

## 1.4.0 (2021-05-12)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ public partial class AuthorizationCodeCredential : Azure.Core.TokenCredential
{
protected AuthorizationCodeCredential() { }
public AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode) { }
public AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, Azure.Identity.AuthorizationCodeCredentialOptions options) { }
public AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, Azure.Identity.TokenCredentialOptions options) { }
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class AuthorizationCodeCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public AuthorizationCodeCredentialOptions() { }
public System.Uri RedirectUri { get { throw null; } set { } }
}
public static partial class AzureAuthorityHosts
{
public static System.Uri AzureChina { get { throw null; } }
Expand Down
61 changes: 43 additions & 18 deletions sdk/identity/Azure.Identity/src/AuthorizationCodeCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ namespace Azure.Identity
/// </summary>
public class AuthorizationCodeCredential : TokenCredential
{
private readonly IConfidentialClientApplication _confidentialClient;
private readonly ClientDiagnostics _clientDiagnostics;
private readonly MsalConfidentialClient _client;
private readonly string _authCode;
private readonly string _clientId;
private readonly CredentialPipeline _pipeline;
private AuthenticationRecord _record;
private readonly string _redirectUri;
private readonly string _tenantId;

/// <summary>
/// Protected constructor for mocking.
Expand All @@ -45,6 +46,23 @@ public AuthorizationCodeCredential(string tenantId, string clientId, string clie
{
}

/// <summary>
/// Creates an instance of the ClientSecretCredential with the details needed to authenticate against Azure Active Directory with a prefetched authorization code.
/// </summary>
/// <param name="tenantId">The Azure Active Directory tenant (directory) Id of the service principal.</param>
/// <param name="clientId">The client (application) ID of the service principal</param>
/// <param name="clientSecret">A client secret that was generated for the App Registration used to authenticate the client.</param>
/// <param name="authorizationCode">The authorization code obtained from a call to authorize. The code should be obtained with all required scopes.
/// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow for more information.</param>
/// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service.</param>
public AuthorizationCodeCredential(
string tenantId,
string clientId,
string clientSecret,
string authorizationCode,
AuthorizationCodeCredentialOptions options) : this(tenantId, clientId, clientSecret, authorizationCode, options, null)
{ }

/// <summary>
/// Creates an instance of the ClientSecretCredential with the details needed to authenticate against Azure Active Directory with a prefetched authorization code.
/// </summary>
Expand All @@ -55,22 +73,27 @@ public AuthorizationCodeCredential(string tenantId, string clientId, string clie
/// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow for more information.</param>
/// <param name="options">Options that allow to configure the management of the requests sent to the Azure Active Directory service.</param>
public AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, TokenCredentialOptions options)
: this(tenantId, clientId, clientSecret, authorizationCode, options, null)
{ }

internal AuthorizationCodeCredential(string tenantId, string clientId, string clientSecret, string authorizationCode, TokenCredentialOptions options, MsalConfidentialClient client)
{
Validations.ValidateTenantId(tenantId, nameof(tenantId));

if (clientSecret is null) throw new ArgumentNullException(nameof(clientSecret));

_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));

_authCode = authorizationCode ?? throw new ArgumentNullException(nameof(authorizationCode));

_tenantId = tenantId;
Argument.AssertNotNull(clientSecret, nameof(clientSecret));
Argument.AssertNotNull(clientId, nameof(clientId));
Argument.AssertNotNull(authorizationCode, nameof(authorizationCode));
_clientId = clientId;
_authCode = authorizationCode ;
options ??= new TokenCredentialOptions();

_pipeline = CredentialPipeline.GetInstance(options);
_redirectUri = options switch
{
AuthorizationCodeCredentialOptions o => o.RedirectUri?.ToString(),
_ => null
};

_confidentialClient = ConfidentialClientApplicationBuilder.Create(clientId).WithHttpClientFactory(new HttpPipelineClientFactory(_pipeline.HttpPipeline)).WithTenantId(tenantId).WithClientSecret(clientSecret).Build();

_clientDiagnostics = new ClientDiagnostics(options);
_client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, clientSecret, options as ITokenCacheOptions);
}

/// <summary>
Expand Down Expand Up @@ -101,20 +124,22 @@ private async ValueTask<AccessToken> GetTokenImplAsync(bool async, TokenRequestC

try
{
AccessToken token = default;
AccessToken token;

if (_record is null)
{
AuthenticationResult result = await _confidentialClient.AcquireTokenByAuthorizationCode(requestContext.Scopes, _authCode).ExecuteAsync(async, cancellationToken).ConfigureAwait(false);

AuthenticationResult result = await _client
.AcquireTokenByAuthorizationCodeAsync(requestContext.Scopes, _authCode, _tenantId, _redirectUri, async, cancellationToken)
.ConfigureAwait(false);
_record = new AuthenticationRecord(result, _clientId);

token = new AccessToken(result.AccessToken, result.ExpiresOn);
}
else
{
AuthenticationResult result = await _confidentialClient.AcquireTokenSilent(requestContext.Scopes, (AuthenticationAccount)_record).ExecuteAsync(async, cancellationToken).ConfigureAwait(false);

AuthenticationResult result = await _client
.AcquireTokenSilentAsync(requestContext.Scopes, (AuthenticationAccount)_record, _tenantId, _redirectUri, async, cancellationToken)
.ConfigureAwait(false);
token = new AccessToken(result.AccessToken, result.ExpiresOn);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Core.Pipeline;
using Microsoft.Identity.Client;

namespace Azure.Identity
{
/// <summary>
/// Options used to configure the <see cref="AuthorizationCodeCredential"/>.
/// </summary>
public class AuthorizationCodeCredentialOptions : TokenCredentialOptions
{
/// <summary>
/// The redirect Uri that will be sent with the GetToken request.
/// </summary>
public Uri RedirectUri { get; set; }
}
}
56 changes: 52 additions & 4 deletions sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ internal class MsalConfidentialClient : MsalClientBase<IConfidentialClientApplic
/// </summary>
protected MsalConfidentialClient()
: base()
{
}
{ }

public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, string clientSecret, ITokenCacheOptions cacheOptions)
: base(pipeline, tenantId, clientId, cacheOptions)
{
_clientSecret = clientSecret;
}

public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, ITokenCacheOptions cacheOptions)
public MsalConfidentialClient(
CredentialPipeline pipeline,
string tenantId,
string clientId,
ClientCertificateCredential.IX509Certificate2Provider certificateProvider,
bool includeX5CClaimHeader,
ITokenCacheOptions cacheOptions)
: base(pipeline, tenantId, clientId, cacheOptions)
{
_includeX5CClaimHeader = includeX5CClaimHeader;
Expand All @@ -39,7 +44,9 @@ public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, stri

protected override async ValueTask<IConfidentialClientApplication> CreateClientAsync(bool async, CancellationToken cancellationToken)
{
ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId).WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, TenantId).WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline));
ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, TenantId)
.WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline));

if (_clientSecret != null)
{
Expand All @@ -61,5 +68,46 @@ public virtual async ValueTask<AuthenticationResult> AcquireTokenForClientAsync(

return await client.AcquireTokenForClient(scopes).WithSendX5C(_includeX5CClaimHeader).ExecuteAsync(async, cancellationToken).ConfigureAwait(false);
}

public virtual async ValueTask<AuthenticationResult> AcquireTokenSilentAsync(
string[] scopes,
AuthenticationAccount account,
string tenantId,
string redirectUri,
bool async,
CancellationToken cancellationToken)
{
IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false);

var builder = client.AcquireTokenSilent(scopes, account);
if (!string.IsNullOrEmpty(tenantId))
{
builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId);
}
return await builder
.ExecuteAsync(async, cancellationToken)
.ConfigureAwait(false);
}

public virtual async ValueTask<AuthenticationResult> AcquireTokenByAuthorizationCodeAsync(
string[] scopes,
string code,
string tenantId,
string redirectUri,
bool async,
CancellationToken cancellationToken)
{
IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false);

var builder = client.AcquireTokenByAuthorizationCode(scopes, code);

if (!string.IsNullOrEmpty(tenantId))
{
builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId);
}
return await builder
.ExecuteAsync(async, cancellationToken)
.ConfigureAwait(false);
}
}
}
Loading