/// <summary> /// Returns a URL through which a user can be authorized to access Office 365 APIs. /// After the authorization is complete, the user will be redirected back to the URL /// defined by the redirectTo parameter. This can be the same URL as the caller's URL /// (e.g., Request.Url), or it can contain additional query-string parameters /// (e.g., to restore state). /// </summary> internal static string GetAuthorizationUrl(string resourceId, Uri redirectTo) { HttpContext context = System.Web.HttpContext.Current; // To prevent Cross-Site Request Forgery attacks (http://tools.ietf.org/html/rfc6749 section 4.2.1), // it is important to send a randomly-generated value as a state parameter. // This state parameter is saved in a cookie, so it can later be compared with the state // parameter that we receive from the Authorization Server along with the Authorization Code. // The state cookie will also capture information about the resource ID and redirect-to URL, // for use in the Index method (after the login page redirects back to this controller). Office365StateCookieInfo stateCookieInfo = new Office365StateCookieInfo { UniqueId = Guid.NewGuid().ToString(), ResourceId = resourceId, RedirectTo = redirectTo.ToString() }; HttpCookie stateCookie = new HttpCookie(OAuthRequestStateCookiePrefix + stateCookieInfo.UniqueId) { HttpOnly = true, Secure = !context.Request.Url.IsLoopback, Value = JsonConvert.SerializeObject(stateCookieInfo), Expires = DateTime.Now.AddMinutes(10) }; context.Response.Cookies.Add(stateCookie); // Create an OAuth request URL. To avoid introducing auth-related complexity into // individual controllers, the Office365CommonController will handle the entire auth flow, // and only redirect to the original caller on completion. Uri redirectToThisController = new Uri(context.Request.Url, "/Office365Common"); return(String.Format(CultureInfo.InvariantCulture, AuthorizeUrl, Uri.EscapeDataString(AppPrincipalId), Uri.EscapeDataString(resourceId), Uri.EscapeDataString(redirectToThisController.ToString()), Uri.EscapeDataString(stateCookieInfo.UniqueId))); }
/// <summary> /// Returns a URL through which a user can be authorized to access Office 365 APIs. /// After the authorization is complete, the user will be redirected back to the URL /// defined by the redirectTo parameter. This can be the same URL as the caller's URL /// (e.g., Request.Url), or it can contain additional query-string parameters /// (e.g., to restore state). /// </summary> internal static string GetAuthorizationUrl(string resourceId, Uri redirectTo) { HttpContext context = System.Web.HttpContext.Current; // To prevent Cross-Site Request Forgery attacks (http://tools.ietf.org/html/rfc6749 section 4.2.1), // it is important to send a randomly-generated value as a state parameter. // This state parameter is saved in a cookie, so it can later be compared with the state // parameter that we receive from the Authorization Server along with the Authorization Code. // The state cookie will also capture information about the resource ID and redirect-to URL, // for use in the Index method (after the login page redirects back to this controller). Office365StateCookieInfo stateCookieInfo = new Office365StateCookieInfo { UniqueId = Guid.NewGuid().ToString(), ResourceId = resourceId, RedirectTo = redirectTo.ToString() }; HttpCookie stateCookie = new HttpCookie(OAuthRequestStateCookiePrefix + stateCookieInfo.UniqueId) { HttpOnly = true, Secure = !context.Request.Url.IsLoopback, Value = JsonConvert.SerializeObject(stateCookieInfo), Expires = DateTime.Now.AddMinutes(10) }; context.Response.Cookies.Add(stateCookie); // Create an OAuth request URL. To avoid introducing auth-related complexity into // individual controllers, the Office365CommonController will handle the entire auth flow, // and only redirect to the original caller on completion. Uri redirectToThisController = new Uri(context.Request.Url, "/Office365Common"); return String.Format(CultureInfo.InvariantCulture, AuthorizeUrl, Uri.EscapeDataString(AppPrincipalId), Uri.EscapeDataString(resourceId), Uri.EscapeDataString(redirectToThisController.ToString()), Uri.EscapeDataString(stateCookieInfo.UniqueId)); }
/// <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())); } }