Esempio n. 1
0
        /// <inheritdoc/>
        public async Task <AuthenticationResult> GetAuthenticationResultForUserAsync(
            IEnumerable <string> scopes,
            string?tenantId      = null,
            string?userFlow      = null,
            ClaimsPrincipal?user = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            string?         idToken     = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers !);
            ClaimsPrincipal?userClaims  = AppServicesAuthenticationInformation.GetUser(CurrentHttpContext?.Request?.Headers !);
            string          accessToken = await GetAccessTokenForUserAsync(scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false);

            string         expiration     = userClaims.FindFirstValue("exp");
            DateTimeOffset dateTimeOffset = (expiration != null)
                ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiration, CultureInfo.InvariantCulture))
                : DateTimeOffset.Now;

            AuthenticationResult authenticationResult = new AuthenticationResult(
                accessToken,
                isExtendedLifeTimeToken: false,
                userClaims?.GetDisplayName(),
                dateTimeOffset,
                dateTimeOffset,
                userClaims?.GetTenantId(),
                userClaims != null ? new Account(userClaims) : null,
                idToken,
                scopes,
                tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CorrelationId : Guid.Empty);

            return(authenticationResult);
        }
        /// <inheritdoc/>
        public Task <string> GetAccessTokenForUserAsync(
            IEnumerable <string> scopes,
            string?tenantId      = null,
            string?userFlow      = null,
            ClaimsPrincipal?user = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            var    httpContext = CurrentHttpContext;
            string accessToken;

            if (httpContext != null)
            {
                // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
                lock (httpContext)
                {
                    accessToken = GetAccessToken(httpContext.Request.Headers);
                }
            }
            else
            {
                accessToken = string.Empty;
            }

            return(Task.FromResult(accessToken));
        }
        /// <summary>
        /// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal.
        /// </summary>
        /// <param name="application"><see cref="IConfidentialClientApplication"/>.</param>
        /// <param name="claimsPrincipal">Claims principal for the user on behalf of whom to get a token.</param>
        /// <param name="scopes">Scopes for the downstream API to call.</param>
        /// <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>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param>
        private async Task <AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
            IConfidentialClientApplication application,
            ClaimsPrincipal?claimsPrincipal,
            IEnumerable <string> scopes,
            string?authority,
            string?userFlow = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            IAccount?account = null;

            if (_microsoftIdentityOptions.IsB2C && !string.IsNullOrEmpty(userFlow))
            {
                string?nameIdentifierId     = claimsPrincipal?.GetNameIdentifierId();
                string?utid                 = claimsPrincipal?.GetHomeTenantId();
                string?b2cAccountIdentifier = string.Format(CultureInfo.InvariantCulture, "{0}-{1}.{2}", nameIdentifierId, userFlow, utid);
                account = await application.GetAccountAsync(b2cAccountIdentifier).ConfigureAwait(false);
            }
            else
            {
                string?accountIdentifier = claimsPrincipal?.GetMsalAccountId();

                if (accountIdentifier != null)
                {
                    account = await application.GetAccountAsync(accountIdentifier).ConfigureAwait(false);
                }
            }

            return(await GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
                       application,
                       account,
                       scopes,
                       authority,
                       userFlow,
                       tokenAcquisitionOptions).ConfigureAwait(false));
        }
 /// <inheritdoc/>
 public Task <AuthenticationResult> GetAuthenticationResultForAppAsync(
     string scope,
     string?authenticationScheme = null,
     string?tenant = null,
     TokenAcquisitionOptions?tokenAcquisitionOptions = null)
 {
     throw new NotImplementedException();
 }
Esempio n. 5
0
        /// <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.
        /// </summary>
        /// <param name="scope">The scope requested to access a protected API. For this flow (client credentials), the scope
        /// should be of the form "{ResourceIdUri/.default}" for instance <c>https://management.azure.net/.default</c> or, for Microsoft
        /// Graph, <c>https://graph.microsoft.com/.default</c> as the requested scopes are defined statically with the application registration
        /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use
        /// several calls to get tokens for other resources).</param>
        /// <param name="tenant">Enables overriding of the tenant/account for the same identity. This is useful
        /// for multi tenant apps or daemons.</param>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param>
        /// <returns>An access token for the app itself, based on its scopes.</returns>
        public async Task <string> GetAccessTokenForAppAsync(
            string scope,
            string?tenant = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            AuthenticationResult authResult = await GetAuthenticationResultForAppAsync(scope, tenant, tokenAcquisitionOptions).ConfigureAwait(false);

            return(authResult.AccessToken);
        }
Esempio n. 6
0
        /// <inheritdoc/>
        public async Task <string> GetAccessTokenForUserAsync(
            IEnumerable <string> scopes,
            string?tenantId      = null,
            string?userFlow      = null,
            ClaimsPrincipal?user = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            string accessToken = GetAccessToken(CurrentHttpContext?.Request.Headers);

            return(await Task.FromResult(accessToken).ConfigureAwait(false));
        }
        /// <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 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="tenantId">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.</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.</param>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition options object which calls into MSAL .NET.</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 <AuthenticationResult> GetAuthenticationResultForUserAsync(
            IEnumerable <string> scopes,
            string?tenantId      = null,
            string?userFlow      = null,
            ClaimsPrincipal?user = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            if (scopes == null)
            {
                throw new ArgumentNullException(nameof(scopes));
            }

            user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false);

            _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false);

            string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenantId);

            AuthenticationResult?authenticationResult;

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

                if (authenticationResult != null)
                {
                    return(authenticationResult);
                }

                // If access token is null, this is a web app
                return(await GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
                           _application,
                           user,
                           scopes,
                           authority,
                           userFlow)
                       .ConfigureAwait(false));
            }
            catch (MsalUiRequiredException ex)
            {
                // GetAccessTokenForUserAsync is an abstraction that can be called from a web app or a web API
                _logger.LogInformation(ex.Message);

                // Case of the web app: we let the MsalUiRequiredException be caught by the
                // AuthorizeForScopesAttribute exception filter so that the user can consent, do 2FA, etc ...
                throw new MicrosoftIdentityWebChallengeUserException(ex, scopes.ToArray(), userFlow);
            }
        }
Esempio n. 8
0
        private async Task <AuthenticationResult?> GetAuthenticationResultForWebApiToCallDownstreamApiAsync(
            IConfidentialClientApplication application,
            string authority,
            IEnumerable <string> scopes,
            TokenAcquisitionOptions?tokenAcquisitionOptions)
        {
            try
            {
                // In web API, validatedToken will not be null
                JwtSecurityToken?validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI();

                // Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API
                if (validatedToken != null)
                {
                    // In the case the token is a JWE (encrypted token), we use the decrypted token.
                    string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData
                                                : validatedToken.InnerToken.RawData;
                    var builder = application
                                  .AcquireTokenOnBehalfOf(
                        scopes.Except(_scopesRequestedByMsal),
                        new UserAssertion(tokenUsedToCallTheWebApi))
                                  .WithSendX5C(_microsoftIdentityOptions.SendX5C)
                                  .WithAuthority(authority);

                    if (tokenAcquisitionOptions != null)
                    {
                        builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters);
                        builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId);
                        builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh);
                        builder.WithClaims(tokenAcquisitionOptions.Claims);
                        if (tokenAcquisitionOptions.PoPConfiguration != null)
                        {
                            builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration);
                        }
                    }

                    return(await builder.ExecuteAsync()
                           .ConfigureAwait(false));
                }

                return(null);
            }
            catch (MsalUiRequiredException ex)
            {
                _logger.LogInformation(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        LogMessages.ErrorAcquiringTokenForDownstreamWebApi,
                        ex.Message));
                throw;
            }
        }
        /// <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.
        /// </summary>
        /// <param name="scope">The scope requested to access a protected API. For this flow (client credentials), the scope
        /// should be of the form "{ResourceIdUri/.default}" for instance <c>https://management.azure.net/.default</c> or, for Microsoft
        /// Graph, <c>https://graph.microsoft.com/.default</c> as the requested scopes are defined statically with the application registration
        /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use
        /// several calls to get tokens for other resources).</param>
        /// <param name="tenant">Enables overriding of the tenant/account for the same identity. This is useful
        /// for multi tenant apps or daemons.</param>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param>
        /// <returns>An access token for the app itself, based on its scopes.</returns>
        public async Task <string> GetAccessTokenForAppAsync(
            string scope,
            string?tenant = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            if (string.IsNullOrEmpty(scope))
            {
                throw new ArgumentNullException(nameof(scope));
            }

            if (!scope.EndsWith("/.default", true, CultureInfo.InvariantCulture))
            {
                throw new ArgumentException(IDWebErrorMessage.ClientCredentialScopeParameterShouldEndInDotDefault, nameof(scope));
            }

            if (string.IsNullOrEmpty(tenant))
            {
                tenant = _applicationOptions.TenantId;
            }

            if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant))
            {
                throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant));
            }

            // Use MSAL to get the right token to call the API
            _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false);

            string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant);

            AuthenticationResult result;
            var builder = _application
                          .AcquireTokenForClient(new string[] { scope }.Except(_scopesRequestedByMsal))
                          .WithSendX5C(_microsoftIdentityOptions.SendX5C)
                          .WithAuthority(authority);

            if (tokenAcquisitionOptions != null)
            {
                builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters);
                builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId);
                builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh);
                builder.WithClaims(tokenAcquisitionOptions.Claims);
            }

            result = await builder.ExecuteAsync()
                     .ConfigureAwait(false);

            return(result.AccessToken);
        }
        /// <summary>
        /// Gets an access token for a downstream API on behalf of the user whose account is passed as an argument.
        /// </summary>
        /// <param name="application"><see cref="IConfidentialClientApplication"/>.</param>
        /// <param name="account">User IAccount for which to acquire a token.
        /// See <see cref="Microsoft.Identity.Client.AccountId.Identifier"/>.</param>
        /// <param name="scopes">Scopes for the downstream API to call.</param>
        /// <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>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</param>
        private async Task <AuthenticationResult> GetAuthenticationResultForWebAppWithAccountFromCacheAsync(
            IConfidentialClientApplication application,
            IAccount?account,
            IEnumerable <string> scopes,
            string?authority,
            string?userFlow = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            if (scopes == null)
            {
                throw new ArgumentNullException(nameof(scopes));
            }

            var builder = application
                          .AcquireTokenSilent(scopes.Except(_scopesRequestedByMsal), account)
                          .WithSendX5C(_microsoftIdentityOptions.SendX5C);

            if (tokenAcquisitionOptions != null)
            {
                builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters);
                builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId);
                builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh);
                builder.WithClaims(tokenAcquisitionOptions.Claims);
                if (tokenAcquisitionOptions.PoPConfiguration != null)
                {
                    builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration);
                }
            }

            // Acquire an access token as a B2C authority
            if (_microsoftIdentityOptions.IsB2C)
            {
                string b2cAuthority = application.Authority.Replace(
                    new Uri(application.Authority).PathAndQuery,
                    $"/{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}");

                builder.WithB2CAuthority(b2cAuthority)
                .WithSendX5C(_microsoftIdentityOptions.SendX5C);
            }
            else
            {
                builder.WithAuthority(authority);
            }

            return(await builder.ExecuteAsync()
                   .ConfigureAwait(false));
        }
        /// <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 the claims 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="tenantId">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.</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.</param>
        /// <param name="tokenAcquisitionOptions">Options passed-in to create the token acquisition object which calls into MSAL .NET.</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?tenantId      = null,
            string?userFlow      = null,
            ClaimsPrincipal?user = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            AuthenticationResult result =
                await GetAuthenticationResultForUserAsync(
                    scopes,
                    tenantId,
                    userFlow,
                    user,
                    tokenAcquisitionOptions).ConfigureAwait(false);

            return(result.AccessToken);
        }
        /// <inheritdoc/>
        public async Task <string> GetAccessTokenForAppAsync(
            string scope,
            string?tenant = null,
            TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            // We could use MSI
            if (scope is null)
            {
                throw new ArgumentNullException(nameof(scope));
            }

            var app = GetOrCreateApplication();
            AuthenticationResult result = await app.AcquireTokenForClient(new string[] { scope })
                                          .ExecuteAsync()
                                          .ConfigureAwait(false);

            return(result.AccessToken);
        }
Esempio n. 13
0
        /// <inheritdoc/>
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task <AuthenticationResult> GetAuthenticationResultForUserAsync(IEnumerable <string> scopes, string?tenantId = null, string?userFlow = null, ClaimsPrincipal?user = null, TokenAcquisitionOptions?tokenAcquisitionOptions = null)
        {
            throw new NotImplementedException();
        }