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}" }); } }
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)); } }
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); }
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; } }
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); }
// 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." }); }
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}" }); } }
// 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; } }
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.")); }
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.")); } }