Skip to content

Commit

Permalink
ITokenAcquisition.GetAuthenticationResultForUserAsync (#553)
Browse files Browse the repository at this point in the history
* Add `GetAuthenticationResultForUserAsync`
- small refactoring of the methods to return an `AuthenticationResult` instead of the token only
- GetAccessTokenForUserAsync implemented based on `GetAuthenticationResultForUserAsync`

Co-authored-by: jennyf19 <[email protected]>
  • Loading branch information
jmprieur and jennyf19 authored Sep 8, 2020
1 parent 8544ca5 commit a830b03
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 16 deletions.
19 changes: 19 additions & 0 deletions src/Microsoft.Identity.Web/ITokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ Task<string> GetAccessTokenForUserAsync(
string? userFlow = null,
ClaimsPrincipal? user = null);

/// <summary>
/// Typically used from an ASP.NET Core web app or web API controller, this method gets an access token
/// for a downstream API on behalf of the user account which claims are provided in the <see cref="HttpContext.User"/>
/// member of the controller's <see cref="HttpContext"/> parameter.
/// </summary>
/// <param name="scopes">Scopes to request for the downstream API to call.</param>
/// <param name="tenantId">Enables to override the tenant/account for the same identity. This is useful in the
/// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant.</param>
/// <param name="userFlow">Azure AD B2C UserFlow to target.</param>
/// <param name="user">Optional claims principal representing the user. If not provided, will use the signed-in
/// user (in a web app), or the user for which the token was received (in a web API)
/// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in.</param>
/// <returns>An <see cref="AuthenticationResult"/> to call on behalf of the user, the downstream API characterized by its scopes.</returns>
Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
IEnumerable<string> scopes,
string? tenantId = null,
string? userFlow = null,
ClaimsPrincipal? user = null);

/// <summary>
/// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user)
/// using the client credentials flow. See https://aka.ms/msal-net-client-credentials.
Expand Down
44 changes: 41 additions & 3 deletions src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 44 additions & 13 deletions src/Microsoft.Identity.Web/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public async Task<string> GetAccessTokenOnBehalfOfUserAsync(
/// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that
/// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by
/// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived.</remarks>
public async Task<string> GetAccessTokenForUserAsync(
public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
IEnumerable<string> scopes,
string? tenant = null,
string? userFlow = null,
Expand All @@ -231,23 +231,23 @@ public async Task<string> GetAccessTokenForUserAsync(

_application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false);
string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant);
string? accessToken;
AuthenticationResult? authenticationResult;

try
{
// Access token will return if call is from a web API
accessToken = await GetTokenForWebApiToCallDownstreamApiAsync(
authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
_application,
authority,
scopes).ConfigureAwait(false);

if (!string.IsNullOrEmpty(accessToken))
if (authenticationResult != null)
{
return accessToken;
return authenticationResult;
}

// If access token is null, this is a web app
return await GetAccessTokenForWebAppWithAccountFromCacheAsync(
return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
_application,
user,
scopes,
Expand All @@ -266,7 +266,7 @@ public async Task<string> GetAccessTokenForUserAsync(
}
}

private async Task<string?> GetTokenForWebApiToCallDownstreamApiAsync(
private async Task<AuthenticationResult?> GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
IConfidentialClientApplication application,
string authority,
IEnumerable<string> scopes)
Expand All @@ -288,7 +288,7 @@ public async Task<string> GetAccessTokenForUserAsync(
.WithAuthority(authority)
.ExecuteAsync()
.ConfigureAwait(false);
return result.AccessToken;
return result;
}

return null;
Expand Down Expand Up @@ -471,7 +471,7 @@ private async Task<IConfidentialClientApplication> BuildConfidentialClientApplic
/// <param name="authority">(optional) Authority based on a specific tenant for which to acquire a token to access the scopes
/// on behalf of the user described in the claimsPrincipal.</param>
/// <param name="userFlow">Azure AD B2C user flow to target.</param>
private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
private async Task<AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
IConfidentialClientApplication application,
ClaimsPrincipal? claimsPrincipal,
IEnumerable<string> scopes,
Expand All @@ -496,7 +496,7 @@ private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
}
}

return await GetAccessTokenForWebAppWithAccountFromCacheAsync(
return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
application,
account,
scopes,
Expand All @@ -513,7 +513,7 @@ private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
/// <param name="authority">Authority based on a specific tenant for which to acquire a token to access the scopes
/// on behalf of the user.</param>
/// <param name="userFlow">Azure AD B2C user flow.</param>
private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
private async Task<AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
IConfidentialClientApplication application,
IAccount? account,
IEnumerable<string> scopes,
Expand All @@ -540,7 +540,7 @@ private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
.ExecuteAsync()
.ConfigureAwait(false);

return result.AccessToken;
return result;
}

result = await application
Expand All @@ -549,7 +549,7 @@ private async Task<string> GetAccessTokenForWebAppWithAccountFromCacheAsync(
.WithSendX5C(_microsoftIdentityOptions.SendX5C)
.ExecuteAsync()
.ConfigureAwait(false);
return result.AccessToken;
return result;
}

/// <summary>
Expand Down Expand Up @@ -657,5 +657,36 @@ private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalSer

return authority;
}

/// <summary>
/// Typically used from a web app or web API controller, this method retrieves an access token
/// for a downstream API using;
/// 1) the token cache (for web apps and web APis) if a token exists in the cache
/// 2) or the <a href='https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow'>on-behalf-of flow</a>
/// in web APIs, for the user account that is ascertained from claims are provided in the <see cref="HttpContext.User"/>
/// instance of the current HttpContext.
/// </summary>
/// <param name="scopes">Scopes to request for the downstream API to call.</param>
/// <param name="tenant">Enables overriding of the tenant/account for the same identity. This is useful in the
/// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in.</param>
/// <param name="userFlow">Azure AD B2C user flow to target.</param>
/// <param name="user">Optional claims principal representing the user. If not provided, will use the signed-in
/// user (in a web app), or the user for which the token was received (in a web API)
/// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in.</param>
/// <returns>An access token to call the downstream API and populated with this downstream API's scopes.</returns>
/// <remarks>Calling this method from a web API supposes that you have previously called,
/// in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method
/// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that
/// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by
/// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived.</remarks>
public async Task<string> GetAccessTokenForUserAsync(
IEnumerable<string> scopes,
string? tenant = null,
string? userFlow = null,
ClaimsPrincipal? user = null)
{
AuthenticationResult result = await GetAuthenticationResultForUserAsync(scopes, tenant, userFlow, user).ConfigureAwait(false);
return result.AccessToken;
}
}
}

0 comments on commit a830b03

Please sign in to comment.