/// <summary> /// Try to get a new access token for this resource using a refresh token. /// If successful, this method will cache the access token for future use. /// If this fails, return null, signaling the caller to do the OAuth redirect. /// </summary> private static string GetAccessTokenFromRefreshToken(string resourceId) { string refreshToken = Office365Cache.GetRefreshToken().Value; if (refreshToken == null) { // If no refresh token, the caller will need to send the user to do an OAuth redirect. return(null); } // Redeem the refresh token for an access token: try { ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey); string authority = string.Format(CultureInfo.InvariantCulture, OAuthUrl, "common"); AuthenticationContext authContext = new AuthenticationContext(authority); AuthenticationResult result = authContext.AcquireTokenByRefreshToken( refreshToken, AppPrincipalId, credential, resourceId); // Cache the access token and update the refresh token: Office365Cache.GetAccessToken(resourceId).Value = result.AccessToken; Office365Cache.GetRefreshToken().Value = result.RefreshToken; return(result.AccessToken); } catch (ActiveDirectoryAuthenticationException) { // Forget the refresh token and return null, so as to start the OAuth redirect from scratch. Office365Cache.GetRefreshToken().RemoveFromCache(); return(null); } }
/// <summary> /// Send an HTTP request, with authorization. If the request fails due to an unauthorized exception, /// this method will try to renew the access token in serviceInfo and try again. /// </summary> public static async Task <HttpResponseMessage> SendRequestAsync( Office365ServiceInfo serviceInfo, HttpClient client, Func <HttpRequestMessage> requestCreator) { using (HttpRequestMessage request = requestCreator.Invoke()) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", serviceInfo.AccessToken); request.Headers.UserAgent.Add(new ProductInfoHeaderValue(AppPrincipalId, String.Empty)); HttpResponseMessage response = await client.SendAsync(request); // Check if the server responded with "Unauthorized". If so, it might be a real authorization issue, or // it might be due to an expired access token. To be sure, renew the token and try one more time: if (response.StatusCode == HttpStatusCode.Unauthorized) { Office365Cache.GetAccessToken(serviceInfo.ResourceId).RemoveFromCache(); serviceInfo.AccessToken = GetAccessTokenFromRefreshToken(serviceInfo.ResourceId); // Create and send a new request: using (HttpRequestMessage retryRequest = requestCreator.Invoke()) { retryRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", serviceInfo.AccessToken); retryRequest.Headers.UserAgent.Add(new ProductInfoHeaderValue(AppPrincipalId, String.Empty)); response = await client.SendAsync(retryRequest); } } // Return either the original response, or the response from the second attempt: return(response); } }
/// <summary> /// Clears any OAuth-related data, such as access and refresh tokens and state cookies. /// This method should be called as part of your application's logout routine. /// </summary> public static void ClearSession() { Office365Cache.RemoveAllFromCache(); // Also remove the cookies used to store the OAuth request state: foreach (string cookieName in System.Web.HttpContext.Current.Request.Cookies.AllKeys) { if (cookieName.StartsWith(OAuthRequestStateCookiePrefix, StringComparison.Ordinal)) { System.Web.HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddDays(-1); } } }
/// <summary> /// Clears any OAuth-related data, such as access and refresh tokens, and logs out. /// This method should be called as part of your application's logout routine. /// </summary> public static async Task ClearSession() { string authority = string.Format(CultureInfo.InvariantCulture, OAuthUrl, TenantId ?? "common"); AuthenticationContext authContext = new AuthenticationContext(authority); authContext.TokenCacheStore.Clear(); Office365Cache.RemoveAllFromCache(); string requestUrl = string.Format(CultureInfo.InvariantCulture, LogoutUrl, TenantId ?? "common", Uri.EscapeDataString(WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString())); await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.SilentMode, new Uri(requestUrl)); }
/// <summary> /// Obtains an access token for the specified resource, using a cached access token or a refresh token. /// If successful, the token will be cached for future use. /// On failure, this method will return null to signify that the caller must do an OAuth redirect instead. /// </summary> internal static string GetAccessToken(string resourceId) { // Try the cache first: string accessToken = Office365Cache.GetAccessToken(resourceId).Value; if (accessToken != null) { return(accessToken); } // If there is no Access Token in the cache for this resource, check if there is a refresh token // in the cache that can be used to get a new access token. accessToken = GetAccessTokenFromRefreshToken(resourceId); if (accessToken != null) { return(accessToken); } // If neither succeeded, return null to signal a need for an OAuth redirect. return(null); }
/// <summary> /// If the item exists, returns the saved value; otherwise, returns null. /// </summary> internal static object GetFromCache(string name) { return(Office365Cache.GetFromCache(name)); }
internal static void SaveInCache(string name, object value) { Office365Cache.SaveInCache(name, value); }
/// <summary> /// This method will be invoked as a call-back from an authentication service (e.g., https://login.windows.net/). /// It is not intended to be called directly, or to be called without first invoking the "GetAuthorizationUrl" method. /// On completion, the method will cache the refresh token and access tokens, and redirect to the URL /// specified in the state cookie (created by the "GetAuthorizationUrl" method, with its unique ID /// included in the "state" of this method). /// </summary> public ActionResult Index(string code, string error, string error_description, string state) { // NOTE: In production, OAuth must be done over a secure HTTPS connection. if (Request.Url.Scheme != "https" && !Request.Url.IsLoopback) { const string message = "Invalid URL. Please run the app over a secure HTTPS connection."; return(ShowErrorMessage(message, message + " URL: " + Request.Url.ToString())); } // Ensure that there is a state cookie on the the request. HttpCookie stateCookie = Request.Cookies[OAuthRequestStateCookiePrefix + state]; if (stateCookie == null) { Office365Cache.RemoveAllFromCache(); const string message = "An authentication error has occurred. Please return to the previous page and try again."; string errorDetail = "Missing OAuth state cookie." + " URL: " + Request.Url.ToString(); return(ShowErrorMessage(message, errorDetail)); } const string genericAuthenticationErrorMessage = "An authentication error has occurred."; // Retrieve the unique ID from the saved cookie, and compare it with the state parameter returned by // the Azure Active Directory Authorization endpoint: Office365StateCookieInfo stateCookieInfo = JsonConvert.DeserializeObject <Office365StateCookieInfo>(stateCookie.Value); if (stateCookieInfo.UniqueId != state) { // State is mismatched, error Office365Cache.RemoveAllFromCache(); string errorDetail = "OAuth state cookie mismatch." + " URL: " + Request.Url.ToString(); return(ShowErrorMessage(genericAuthenticationErrorMessage, errorDetail)); } // State check complete, clear the cookie: stateCookie.Expires = DateTime.Now.AddDays(-1); Response.Cookies.Set(stateCookie); // Handle error codes returned from the Authorization Server, if any: if (error != null) { Office365Cache.RemoveAllFromCache(); return(ShowErrorMessage(genericAuthenticationErrorMessage, error + ": " + error_description + " URL: " + Request.Url.ToString())); } // If still here, redeem the authorization code for an access token: try { ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey); string authority = string.Format(CultureInfo.InvariantCulture, OAuthUrl, "common"); AuthenticationContext authContext = new AuthenticationContext(authority); AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode( code, new Uri(Request.Url.GetLeftPart(UriPartial.Path)), credential); // Cache the access token and refresh token Office365Cache.GetAccessToken(stateCookieInfo.ResourceId).Value = result.AccessToken; Office365Cache.GetRefreshToken().Value = result.RefreshToken; // Also save the Tenant ID and User ID SaveInCache("TenantId", result.TenantId); SaveInCache("UserId", result.UserInfo.UserId); return(Redirect(stateCookieInfo.RedirectTo)); } catch (ActiveDirectoryAuthenticationException ex) { return(ShowErrorMessage(genericAuthenticationErrorMessage, "URL: " + Request.Url.ToString() + " Exception: " + ex.ToString())); } }
public void RemoveFromCache() { Office365Cache.RemoveFromCache(_name); }