예제 #1
0
        public static async Task GetTokenOnBehalfOfAsync(string authHeader, ILogger log)
        {
            logger = log;
            if (string.IsNullOrEmpty(authHeader))
            {
                throw new MsalException("missing_auth", "Authorization header is not present on request.");
            }

            // Parse the auth header
            var parsedHeader = AuthenticationHeaderValue.Parse(authHeader);

            if (parsedHeader.Scheme.ToLower() != "bearer")
            {
                throw new MsalException("invalid_scheme", "Authorization header is missing the 'bearer' scheme.");
            }

            var confidentialClient = new ConfidentialClientApplication(notifAppId,
                                                                       authority, redirectUri, notifClientCreds, BlobTokenCache.GetMsalCacheInstance(), null);

            //Logger.LogCallback = AuthLog;
            //Logger.Level = Microsoft.Identity.Client.LogLevel.Verbose;
            //Logger.PiiLoggingEnabled = true;
            var userAssertion = new UserAssertion(parsedHeader.Parameter);

            try
            {
                var result = await confidentialClient.AcquireTokenOnBehalfOfAsync(notifScopes, userAssertion);
            }
            catch (Exception ex)
            {
                logger.LogError($"Error getting OBO token: {ex.Message}");
                throw ex;
            }
        }
        // Gets an access token. First tries to get the access token from the token cache.
        // Using password (secret) to authenticate. Production apps should use a certificate.
        public async Task <string> GetUserAccessTokenAsync(string userId)
        {
            if (_userTokenCache == null)
            {
                _userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
            }

            var cca = new ConfidentialClientApplication(
                _clientId,
                _redirectUri,
                _credential,
                _userTokenCache,
                null);

            var originalToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");

            var userAssertion = new UserAssertion(originalToken,
                                                  "urn:ietf:params:oauth:grant-type:jwt-bearer");

            try
            {
                var result = await cca.AcquireTokenOnBehalfOfAsync(_scopes, userAssertion);

                return(result.AccessToken);
            }
            catch (Exception ex)
            {
                // Unable to retrieve the access token silently.
                throw new ServiceException(new Error
                {
                    Code    = GraphErrorCode.AuthenticationFailure.ToString(),
                    Message = $"Caller needs to authenticate. Unable to retrieve the access token silently. error: {ex}"
                });
            }
        }
예제 #3
0
        public static async Task <string> GetTokenOnBehalfOfAsync(string authHeader)
        {
            if (string.IsNullOrEmpty(authHeader))
            {
                throw new MsalException("missing_auth", "Authorization header is not present on request.");
            }

            // Parse the auth header
            var parsedHeader = AuthenticationHeaderValue.Parse(authHeader);

            if (parsedHeader.Scheme.ToLower() != "bearer")
            {
                throw new MsalException("invalid_scheme", "Authorization header is missing the 'bearer' scheme.");
            }

            // Create an assertion based on the provided token
            var userAssertion = new UserAssertion(parsedHeader.Parameter);

            var confidentialClient = new ConfidentialClientApplication(appId, authority, clientCreds, null, null);

            // Exchange the provided token for a Graph token
            var result = await confidentialClient.AcquireTokenOnBehalfOfAsync(scopes, userAssertion, authority);

            return(result.AccessToken);
        }
        public async Task <IActionResult> GetGraphAccessTokenAsync()
        {
            try
            {
                ClientCredential clientCred      = new ClientCredential(appKey);
                string           userAccessToken = ((ClaimsIdentity)HttpContext.User.Identity).BootstrapContext.ToString();
                UserAssertion    userAssertion   = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer");


                const string siteUrl = "smartlink.azurewebsites.net";

                ConfidentialClientApplication cca =
                    new ConfidentialClientApplication(clientId,
                                                      $"https://{siteUrl}", clientCred, null, null);


                AuthenticationResult result = await cca.AcquireTokenOnBehalfOfAsync(new[] { resourceId }, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0");

                return(Ok(result.AccessToken));
            }
            catch (Exception ex)
            {
                return(BadRequest(ex.Message));
            }
        }
예제 #5
0
        public async Task <string> GetTokenOnBehalfOfAsync()
        {
            try
            {
                const string siteUrl = "localhost:44334";                // "proposalcreation.azurewebsites.net"; //localhost:44334
                // Get the raw token that the add-in page received from the Office host.
                var bootstrapContext = ((ClaimsIdentity)contextAccessor.HttpContext.User.Identity).BootstrapContext.ToString();

                UserAssertion userAssertion = new UserAssertion(bootstrapContext);

                // Get the access token for MS Graph.
                ClientCredential clientCred       = new ClientCredential(this.secret);
                ConfidentialClientApplication cca =
                    new ConfidentialClientApplication(appId,
                                                      $"https://{siteUrl}", clientCred, null, null);

                // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a
                // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow
                // with the Azure AD V2 endpoint.
                AuthenticationResult result = await cca.AcquireTokenOnBehalfOfAsync(scopes, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0");

                return(result.AccessToken);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public void OBOUserAssertionHashUsernamePassedTest()
        {
            TokenCache    cache         = TokenCacheHelper.CreateCacheWithItems();
            string        someAssertion = "some-assertion-passed-by-developer";
            TokenCacheKey key           = cache.tokenCacheDictionary.Keys.First();

            //update cache entry with hash of an assertion that will not match
            cache.tokenCacheDictionary[key].UserAssertionHash =
                new CryptographyHelper().CreateSha256Hash(someAssertion);

            ConfidentialClientApplication app = new ConfidentialClientApplication(TestConstants.DefaultClientId,
                                                                                  TestConstants.DefaultRedirectUri, new ClientCredential(TestConstants.DefaultClientSecret), new TokenCache());

            app.UserTokenCache = cache;

            //this is a fail safe. No call should go on network
            HttpMessageHandlerFactory.MockHandler = new MockHttpMessageHandler()
            {
                Method          = HttpMethod.Post,
                ResponseMessage =
                    MockHelpers.CreateInvalidGrantTokenResponseMessage()
            };

            UserAssertion assertion          = new UserAssertion(someAssertion, AssertionType, key.DisplayableId);
            Task <AuthenticationResult> task = app.AcquireTokenOnBehalfOfAsync(key.Scope.AsArray(),
                                                                               assertion, key.Authority, TestConstants.DefaultPolicy);
            AuthenticationResult result = task.Result;

            Assert.IsNotNull(result);
            Assert.AreEqual(key.UniqueId, result.User.UniqueId);
            Assert.AreEqual(key.DisplayableId, result.User.DisplayableId);
            Assert.AreEqual(HashAccessToken,
                            cache.tokenCacheDictionary[key].UserAssertionHash);
        }
예제 #7
0
        public async Task <string> GetProposalManagerTokenOnBehalfOfAsync()
        {
            try
            {
                const string siteUrl = "proposalcreation.azurewebsites.net";
                // Get the raw token that the add-in page received from the Office host.
                var bootstrapContext = ((ClaimsIdentity)contextAccessor.HttpContext.User.Identity).BootstrapContext.ToString();

                var userAssertion = new UserAssertion(bootstrapContext, "urn:ietf:params:oauth:grant-type:jwt-bearer");

                // Get the access token for MS Graph.
                var clientCred = new ClientCredential(secret);
                var cca        =
                    new ConfidentialClientApplication(appId,
                                                      $"https://{siteUrl}", clientCred, null, null);

                // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a
                // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow
                // with the Azure AD V2 endpoint.
                var apiScope = $"api://{proposalManagerApiId}/access_as_user";
                var result   = await cca.AcquireTokenOnBehalfOfAsync(new[] { apiScope }, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0");

                return(result.AccessToken);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
예제 #8
0
        private async Task <string> GetUserAccessTokenAsync(IEnumerable <string> scopes)
        {
            var userId    = this.GetUserObjectId();
            var appSecret = await this.keyVaultService.GetSecret(this.config.AppSecretKeyVaultKey);

            var credential = new ClientCredential(appSecret);

            var request    = this.httpContextAccessor.HttpContext.Request;
            var currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase);

            var tokenCache  = new DistributedTokenCacheHelper(this.cache, this.dataProtectionProvider, userId).GetTokenCache();
            var application = new ConfidentialClientApplication(this.config.ClientId, this.config.Authority, currentUri, credential, tokenCache, null);

            AuthenticationResult result;

            try
            {
                IAccount account = await application.GetAccountAsync($"{userId}.{this.config.TenantId}");

                result = await application.AcquireTokenSilentAsync(scopes, account);
            }
            catch
            {
                var token = await this.httpContextAccessor.HttpContext.GetTokenAsync("access_token");

                var userAssertion = new UserAssertion(token, "urn:ietf:params:oauth:grant-type:jwt-bearer");
                result = await application.AcquireTokenOnBehalfOfAsync(scopes, userAssertion);
            }

            return(result.AccessToken);
        }
예제 #9
0
        // GET api/values
        public async Task <IEnumerable <string> > Get()
        {
            // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user".
            string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' ');
            if (addinScopes.Contains("access_as_user"))
            {
                // Get the raw token that the add-in page received from the Office host.
                var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext
                                       as BootstrapContext;
                UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token);

                // Get the access token for MS Graph.
                ClientCredential clientCred       = new ClientCredential(ConfigurationManager.AppSettings["ida:Password"]);
                ConfidentialClientApplication cca =
                    new ConfidentialClientApplication(ConfigurationManager.AppSettings["ida:ClientID"],
                                                      "https://localhost:44355", clientCred, null, null);
                string[]             graphScopes = { "Files.Read.All" };
                AuthenticationResult result      = null;
                try
                {
                    // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a
                    // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow
                    // with the Azure AD V2 endpoint.
                    result = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0");
                }
                catch (MsalUiRequiredException e)
                {
                    // If multi-factor authentication is required by the MS Graph resource an
                    // the user has not yet provided it, AAD will throw an exception containing a
                    // Claims property.
                    if (String.IsNullOrEmpty(e.Claims))
                    {
                        throw e;
                    }
                    else
                    {
                        // The Claims property value must be passed to the client which will pass it
                        // to the Office host, which will then include it in a request for a new token.
                        // AAD will prompt the user for all required forms of authentication.
                        throw new HttpException(e.Claims);
                    }
                }

                // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed.
                var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3");
                var getFilesResult       = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken);

                // The returned JSON includes OData metadata and eTags that the add-in does not use.
                // Return to the client-side only the filenames.
                List <string> itemNames = new List <string>();
                foreach (OneDriveItem item in getFilesResult)
                {
                    itemNames.Add(item.Name);
                }
                return(itemNames);
            }
            return(new string[] { "Error", "Microsoft Office does not have permission to get Microsoft Graph data on behalf of the current user." });
        }
예제 #10
0
        protected async Task <string> getToken()
        {
            UserAssertion userAssertion;
            var           session = (base.GetSession() as AuthUserSession);

            if (session != null && !string.IsNullOrWhiteSpace(session.RequestTokenSecret))
            {
                return(session.RequestTokenSecret);
            }

            if (this.Cache.Get <string>("tk") != null)
            {
                return(this.Cache.Get <string>("tk"));
            }



            var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;

            //	SecurityToken st = bootstrapContext.SecurityToken;

            userAssertion = new UserAssertion(bootstrapContext.Token);

            ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ida:Password"]);

            ConfidentialClientApplication cca =
                new ConfidentialClientApplication(ConfigurationManager.AppSettings["ida:ClientID"],
                                                  ConfigurationManager.AppSettings["ida:RedirectUri"], clientCred, null, null);

            string[]             graphScopes = { "Files.Read.All" };
            AuthenticationResult result      = null;

            try
            {
                // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a
                // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow
                // with the Azure AD V2 endpoint.
                result = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion, "https://login.microsoftonline.com/dd30c61c-1361-4509-82d6-fab96f7102a2/oauth2/v2.0");

                if (session != null)
                {
                    base.Cache.Add <string>("tk", result.AccessToken);
                }
                return(result.AccessToken);
            }
            catch (MsalServiceException e)
            {
                throw;
            }
        }
        /// <summary>
        /// This method is used to initialize the on behalf token, currently not in use although the code is left
        /// to show how azure vault can be used to store secrets
        /// </summary>
        /// <param name="userId">UPN of the on behalf user</param>
        /// <returns>access token</returns>
        public async Task <string> SetOnBehalfAccessTokenAsync(string userId)
        {
            if (_userTokenCache == null)
            {
                _userTokenCache = new TokenCache();
            }

            var originalToken = await _httpContextAccessor.HttpContext.GetTokenAsync("AzureAdBearer", "access_token");

            var userContextClient = new ConfidentialClientApplication(
                _clientId,
                _redirectUri,
                _credential,
                _userTokenCache,
                null);

            var userAssertion = new UserAssertion(originalToken,
                                                  "urn:ietf:params:oauth:grant-type:jwt-bearer");

            try
            {
                var result = await userContextClient.AcquireTokenOnBehalfOfAsync(_scopes, userAssertion);

                Guard.Against.NullOrEmpty(result.AccessToken, "GraphAuthProvider_SetOnBehalfAccessTokenAsync result.AccessToken is null or empty");

                //var userTokenCacheSerialized = userTokenCache.Serialize();

                // Store token in secured vault
                await _azureKeyVaultService.SetValueInVaultAsync(VaultKeys.AccessToken, result.AccessToken);

                await _azureKeyVaultService.SetValueInVaultAsync(VaultKeys.Expiration, result.ExpiresOn.ToString());

                await _azureKeyVaultService.SetValueInVaultAsync(VaultKeys.Upn, userId);

                return(result.AccessToken);
            }
            catch (Exception ex)
            {
                // Unable to retrieve the access token silently.
                throw new ServiceException(new Error
                {
                    Code    = GraphErrorCode.AuthenticationFailure.ToString(),
                    Message = $"GraphAuthProvider_SetOnBehalfAccessTokenAsync Caller needs to authenticate. Unable to retrieve the access token silently. error: {ex}"
                });
            }
        }
        // Gets an access token. First tries to get the access token from the token cache.
        // Using password (secret) to authenticate. Production apps should use a certificate.
        public async Task <string> GetUserAccessTokenAsync(string userId, bool appOnBehalf = false)
        {
            if (_userTokenCache == null)
            {
                _userTokenCache = new TokenCache();
            }

            var originalToken = "";

            var userContextClient = new ConfidentialClientApplication(
                _clientId,
                _redirectUri,
                _credential,
                _userTokenCache,
                null);

            if (String.IsNullOrEmpty(originalToken))
            {
                originalToken = await _httpContextAccessor.HttpContext.GetTokenAsync("AzureAdBearer", "access_token");

                //originalToken = _httpContextAccessor.HttpContext.Request.Headers["authorization"][0].Split(' ')[1];
            }

            var userAssertion = new UserAssertion(originalToken,
                                                  "urn:ietf:params:oauth:grant-type:jwt-bearer");

            try
            {
                var result = await userContextClient.AcquireTokenOnBehalfOfAsync(_scopes, userAssertion);

                Guard.Against.NullOrEmpty(result.AccessToken, "GraphAuthProvider_GetUserAccessTokenAsync result.AccessToken is null or empty");

                return(result.AccessToken);
            }
            catch (Exception ex)
            {
                // Unable to retrieve the access token silently.
                throw new ServiceException(new Error
                {
                    Code    = GraphErrorCode.AuthenticationFailure.ToString(),
                    Message = $"GraphAuthProvider_GetUserAccessTokenAsync Caller needs to authenticate. Unable to retrieve the access token silently. error: {ex}"
                });
            }
        }
예제 #13
0
        // Gets an access token. First tries to get the token from the token cache.
        public async Task <string> GetUserAccessTokenAsync(AzureConfiguration azureConfiguration, string jwtToken)
        {
            try
            {
                ConfidentialClientApplication cca = new ConfidentialClientApplication(
                    azureConfiguration.ClientId,
                    azureConfiguration.RedirectUri,
                    new ClientCredential(azureConfiguration.ClientSecret),
                    null,
                    null);

                AuthenticationResult result = await cca.AcquireTokenOnBehalfOfAsync(azureConfiguration.Scope.Split(" "), new UserAssertion(jwtToken));

                return(result.AccessToken);
            }
            catch (Exception exception)
            {
                Trace.WriteLine(exception.Message);
                return(null);
            }
        }
        public void OBOUserAssertionHashNotFoundTest()
        {
            TokenCache    cache         = TokenCacheHelper.CreateCacheWithItems();
            string        someAssertion = "some-assertion-passed-by-developer";
            TokenCacheKey key           = cache.tokenCacheDictionary.Keys.First();

            //update cache entry with hash of an assertion that will not match
            cache.tokenCacheDictionary[key].UserAssertionHash =
                new CryptographyHelper().CreateSha256Hash(someAssertion + "-but-not-in-cache");

            ConfidentialClientApplication app = new ConfidentialClientApplication(TestConstants.DefaultClientId,
                                                                                  TestConstants.DefaultRedirectUri, new ClientCredential(TestConstants.DefaultClientSecret), new TokenCache());

            app.UserTokenCache = cache;

            string[] scope = { "mail.read" };
            HttpMessageHandlerFactory.MockHandler = new MockHttpMessageHandler()
            {
                Method          = HttpMethod.Post,
                ResponseMessage =
                    MockHelpers.CreateSuccessTokenResponseMessage("unique_id_3", "*****@*****.**", "root_id_3",
                                                                  scope)
            };

            UserAssertion assertion          = new UserAssertion(someAssertion, AssertionType);
            Task <AuthenticationResult> task = app.AcquireTokenOnBehalfOfAsync(key.Scope.AsArray(),
                                                                               assertion, key.Authority, TestConstants.DefaultPolicy);
            AuthenticationResult result = task.Result;

            Assert.IsNotNull(result);
            Assert.AreEqual("unique_id_3", result.User.UniqueId);
            Assert.AreEqual("*****@*****.**", result.User.DisplayableId);

            //check for new assertion Hash
            AuthenticationResultEx resultEx =
                cache.tokenCacheDictionary.Values.First(r => r.Result.User.UniqueId.Equals("unique_id_3"));

            Assert.AreEqual(HashAccessToken, resultEx.UserAssertionHash);
        }
        /// <summary>
        /// Given a valid AccessToken and set of desired scopes and an Access Token, gets a new on-behalf-of Access Token.
        /// </summary>
        /// <param name="accessToken">A valid Access Token</param>
        /// <param name="scopes">List of desired scopes for the Access Token.</param>
        /// <returns>The AuthenticationResult with the resultant Access Token if the on-behalf-of auth was successful</returns>
        /// <exception cref="Microsoft.Identity.Client.MsalException">Throws an MsalException if there are any authentication issues</exception>
        public async Task <AuthenticationResult> GetOnBehalfOfAccessTokenAsync(string accessToken, IEnumerable <string> scopes)
        {
            try
            {
                logger.LogInformation("Getting On-Behalf-of Access Token using accessToken: <EXCLUDED FOR SECURITY>...");

                var clientTokenCache = new TokenCache();
                var userTokenCache   = tokenCacheService.FetchUserCache();
                var appTokenCache    = new TokenCache();

                var msalApplication = new ConfidentialClientApplication(configuration["ClientId"], configuration["RedirectUri"], new ClientCredential(configuration["ClientSecret"]), userTokenCache, appTokenCache);
                var user            = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer");
                var result          = await msalApplication.AcquireTokenOnBehalfOfAsync(scopes, user, $"https://login.microsoftonline.com/{configuration["TenantId"]}");

                return(result);
            }
            catch (MsalException msalException)
            {
                logger.LogError(new EventId(0), msalException, $"Exception On-Behalf-of Access Token using accessToken: <EXCLUDED FOR SECURITY>. Look at the app registration and make sure you have the correct ClientId, ClientSecret and RedirectUri configured in the app settings...");
                throw msalException;
            }
        }
예제 #16
0
        public async Task JsonWebTokenWithX509PublicCertSendCertificateOnBehalfOfTestAsync()
        {
            using (var httpManager = new MockHttpManager())
            {
                var serviceBundle = ServiceBundle.CreateWithCustomHttpManager(httpManager);
                SetupMocks(httpManager);

                var certificate = new X509Certificate2(
                    ResourceHelper.GetTestResourceRelativePath("valid_cert.pfx"),
                    MsalTestConstants.DefaultPassword);
                var clientAssertion  = new ClientAssertionCertificate(certificate);
                var clientCredential = new ClientCredential(clientAssertion);
                var app = new ConfidentialClientApplication(
                    serviceBundle,
                    MsalTestConstants.ClientId,
                    ClientApplicationBase.DefaultAuthority,
                    MsalTestConstants.RedirectUri,
                    clientCredential,
                    _cache,
                    _cache)
                {
                    ValidateAuthority = false
                };
                var userAssertion = new UserAssertion(MsalTestConstants.DefaultAccessToken);

                //Check for x5c claim
                httpManager.AddMockHandler(X5CMockHandler);
                AuthenticationResult result =
                    await(app as IConfidentialClientApplicationWithCertificate).AcquireTokenOnBehalfOfWithCertificateAsync(
                        MsalTestConstants.Scope,
                        userAssertion).ConfigureAwait(false);
                Assert.IsNotNull(result.AccessToken);

                result = await app.AcquireTokenOnBehalfOfAsync(MsalTestConstants.Scope, userAssertion).ConfigureAwait(false);

                Assert.IsNotNull(result.AccessToken);
            }
        }
        // GET api/values
        public async Task <HttpResponseMessage> Get()
        {
            // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user".
            string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' ');
            if (addinScopes.Contains("access_as_user"))
            {
                // Get the raw token that the add-in page received from the Office host.
                var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext
                                       as BootstrapContext;
                UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token);

                // Get the access token for MS Graph.
                ClientCredential clientCred       = new ClientCredential(ConfigurationManager.AppSettings["ida:Password"]);
                ConfidentialClientApplication cca =
                    new ConfidentialClientApplication(ConfigurationManager.AppSettings["ida:ClientID"],
                                                      "https://localhost:44355", clientCred, null, null);
                string[]             graphScopes = { "Files.Read.All" };
                AuthenticationResult result      = null;
                try
                {
                    // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a
                    // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow
                    // with the Azure AD V2 endpoint.
                    result = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0");
                }
                catch (MsalServiceException e)
                {
                    // If multi-factor authentication is required by the MS Graph resource and the user
                    // has not yet provided it, AAD will return "400 Bad Request" with error AADSTS50076
                    // and a Claims property. MSAL throws a MsalUiRequiredException (which inherits
                    // from MsalServiceException) with this information. The Claims property value must
                    // be passed to the client which should pass it to the Office host, which then
                    // includes it in a request for a new token. AAD will prompt the user for all
                    // required forms of authentication.
                    if (e.Message.StartsWith("AADSTS50076"))
                    {
                        // The APIs that create HTTP Responses from exceptions don't know about the
                        // Claims property, so they don't include it. We have to manually create a message
                        // that includes it. A custom Message property, however, blocks the creation of an
                        // ExceptionMessage property, so the only way to get the error AADSTS50076 to the
                        // client is to add it to the custom Message. JavaScript in the client will need
                        // to discover if a response has a Message or ExceptionMessage, so it knows which
                        // to read.
                        string responseMessage = String.Format("{{\"AADError\":\"AADSTS50076\",\"Claims\":{0}}}", e.Claims);
                        return(SendErrorToClient(HttpStatusCode.Forbidden, null, responseMessage));
                    }

                    // If the call to AAD contained at least one scope (permission) for which neither
                    // the user nor a tenant administrator has consented (or consent was revoked.
                    // AAD will return "400 Bad Request" with error AADSTS65001. MSAL throws a
                    // MsalUiRequiredException with this information. The client should re-call
                    // getAccessTokenAsync with the option { forceConsent: true }.
                    if ((e.Message.StartsWith("AADSTS65001"))

                        // If the call to AAD contained at least one scope that AAD does not recognize,
                        // AAD returns "400 Bad Request" with error AADSTS70011. MSAL throws a
                        // MsalUiRequiredException (which inherits from MsalServiceException) with this
                        // information. The client should inform the user.
                        || (e.Message.StartsWith("AADSTS70011: The provided value for the input parameter 'scope' is not valid.")))
                    {
                        return(SendErrorToClient(HttpStatusCode.Forbidden, e, null));
                    }
                    else
                    {
                        // Rethrowing the MsalServiceException will not relay the original
                        // "400 Bad Request" exception to the client. Instead a "500 Server Error"
                        // is sent.
                        throw e;
                    }
                }

                // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed.
                // Note that the parameter is hardcoded. If you reuse this code in a production add-in and any part of the query parameter comes
                // from user input, be sure that it is sanitized so that it cannot be used in a Response header injection attack.
                var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3");

                IEnumerable <OneDriveItem> filesResult;
                try
                {
                    filesResult = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken);
                }

                // If the token is invalid, MS Graph sends a "401 Unauthorized" error with the code
                // "InvalidAuthenticationToken". ASP.NET then throws a RuntimeBinderException. This
                // is also what happens when the token is expired, although MSAL should prevent that
                // from ever happening. In either case, the client should start the process over by
                // re-calling getAccessTokenAsync.
                catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
                {
                    return(SendErrorToClient(HttpStatusCode.Unauthorized, e, null));
                }

                // The returned JSON includes OData metadata and eTags that the add-in does not use.
                // Return to the client-side only the filenames.
                List <string> itemNames = new List <string>();
                foreach (OneDriveItem item in filesResult)
                {
                    itemNames.Add(item.Name);
                }

                var requestMessage = new HttpRequestMessage();
                requestMessage.SetConfiguration(new HttpConfiguration());
                var response = requestMessage.CreateResponse <List <string> >(HttpStatusCode.OK, itemNames);
                return(response);
            }
            // The token from the client does not have "access_as_user" permission.
            return(SendErrorToClient(HttpStatusCode.Unauthorized, null, "Missing access_as_user."));
        }
예제 #18
0
        private async Task <IHttpActionResult> SaveAttachmentsWithSsoToken(SaveAttachmentRequest request)
        {
            // First retrieve the raw access token
            var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;

            if (bootstrapContext != null)
            {
                // Use MSAL to invoke the on-behalf-of flow to exchange token for a Graph token
                UserAssertion    userAssertion    = new UserAssertion(bootstrapContext.Token);
                ClientCredential clientCred       = new ClientCredential(ConfigurationManager.AppSettings["ida:AppPassword"]);
                ConfidentialClientApplication cca = new ConfidentialClientApplication(
                    ConfigurationManager.AppSettings["ida:AppId"],
                    ConfigurationManager.AppSettings["ida:RedirectUri"],
                    clientCred, null, null);

                string[] graphScopes = { "Files.ReadWrite", "Mail.Read" };

                AuthenticationResult authResult = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion);

                // Initialize a Graph client
                GraphServiceClient graphClient = new GraphServiceClient(
                    new DelegateAuthenticationProvider(
                        (requestMessage) => {
                    // Add the OneDrive access token to each outgoing request
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
                    return(Task.FromResult(0));
                }));

                foreach (string attachmentId in request.attachmentIds)
                {
                    var attachment = await graphClient.Me.Messages[request.messageId].Attachments[attachmentId].Request().GetAsync();

                    // Is this a file or an Outlook item?
                    if (string.Compare(attachment.ODataType, "#microsoft.graph.itemAttachment") == 0)
                    {
                        // Re-request the attachment with the item expanded
                        var itemAttachment = await graphClient.Me.Messages[request.messageId].Attachments[attachmentId].Request()
                                             .Expand("microsoft.graph.itemAttachment/item").GetAsync() as ItemAttachment;

                        // Serialize the item to JSON and save to OneDrive
                        string       jsonItem   = JsonConvert.SerializeObject(itemAttachment.Item);
                        MemoryStream fileStream = new MemoryStream();
                        StreamWriter sw         = new StreamWriter(fileStream);
                        sw.Write(jsonItem);
                        sw.Flush();
                        fileStream.Position = 0;
                        bool success = await SaveFileToOneDrive(graphClient, itemAttachment.Name + ".json", fileStream);

                        if (!success)
                        {
                            return(BadRequest(string.Format("Could not save {0} to OneDrive", itemAttachment.Name)));
                        }
                    }
                    else
                    {
                        var fileAttachment = attachment as FileAttachment;

                        // For files, we can build a stream directly from ContentBytes
                        if (fileAttachment.Size < (4 * 1024 * 1024))
                        {
                            MemoryStream fileStream = new MemoryStream(fileAttachment.ContentBytes);
                            bool         success    = await SaveFileToOneDrive(graphClient, fileAttachment.Name, fileStream);

                            if (!success)
                            {
                                return(BadRequest(string.Format("Could not save {0} to OneDrive", fileAttachment.Name)));
                            }
                        }
                        else
                        {
                            // TODO: Add code here to handle larger files. See:
                            // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/item_createuploadsession
                            // and
                            // https://github.com/microsoftgraph/aspnet-snippets-sample/blob/master/Graph-ASPNET-46-Snippets/Microsoft%20Graph%20ASPNET%20Snippets/Models/FilesService.cs
                            return(BadRequest("File is too large for simple upload."));
                        }
                    }
                }

                return(Ok());
            }
            else
            {
                return(BadRequest("Could not retrieve access token from request."));
            }
        }