diff options
Diffstat (limited to 'src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs')
-rw-r--r-- | src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs | 129 |
1 files changed, 98 insertions, 31 deletions
diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs index 06ca161..d33913a 100644 --- a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs +++ b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.AspNet { using System; using System.Diagnostics.CodeAnalysis; using System.Web; + using System.Web.Security; using DotNetOpenAuth.AspNet.Clients; using DotNetOpenAuth.Messaging; @@ -23,6 +24,16 @@ namespace DotNetOpenAuth.AspNet { private const string ProviderQueryStringName = "__provider__"; /// <summary> + /// The query string name for session id. + /// </summary> + private const string SessionIdQueryStringName = "__sid__"; + + /// <summary> + /// The cookie name for session id. + /// </summary> + private const string SessionIdCookieName = "__csid__"; + + /// <summary> /// The _authentication provider. /// </summary> private readonly IAuthenticationClient authenticationProvider; @@ -140,44 +151,55 @@ namespace DotNetOpenAuth.AspNet { Uri uri; if (!string.IsNullOrEmpty(returnUrl)) { uri = UriHelper.ConvertToAbsoluteUri(returnUrl, this.requestContext); - } else { + } + else { uri = this.requestContext.Request.GetPublicFacingUrl(); } // attach the provider parameter so that we know which provider initiated // the login when user is redirected back to this page uri = uri.AttachQueryStringParameter(ProviderQueryStringName, this.authenticationProvider.ProviderName); - this.authenticationProvider.RequestAuthentication(this.requestContext, uri); - } - /// <summary> - /// Checks if user is successfully authenticated when user is redirected back to this user. - /// </summary> - /// <returns>The result of the authentication.</returns> - public AuthenticationResult VerifyAuthentication() { - AuthenticationResult result = this.authenticationProvider.VerifyAuthentication(this.requestContext); - if (!result.IsSuccessful) { - // if the result is a Failed result, creates a new Failed response which has providerName info. - result = new AuthenticationResult( - isSuccessful: false, - provider: this.authenticationProvider.ProviderName, - providerUserId: null, - userName: null, - extraData: null); + // Guard against XSRF attack by injecting session id into the redirect url and response cookie. + // Upon returning from the external provider, we'll compare the session id value in the query + // string and the cookie. If they don't match, we'll reject the request. + string sessionId = Guid.NewGuid().ToString("N"); + uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId); + + var xsrfCookie = new HttpCookie(SessionIdCookieName, sessionId) { + HttpOnly = true + }; + if (FormsAuthentication.RequireSSL) { + xsrfCookie.Secure = true; } + this.requestContext.Response.Cookies.Add(xsrfCookie); - return result; + // issue the redirect to the external auth provider + this.authenticationProvider.RequestAuthentication(this.requestContext, uri); } /// <summary> /// Checks if user is successfully authenticated when user is redirected back to this user. /// </summary> /// <param name="returnUrl">The return Url which must match exactly the Url passed into RequestAuthentication() earlier.</param> + /// <remarks> + /// This returnUrl parameter only applies to OAuth2 providers. For other providers, it ignores the returnUrl parameter. + /// </remarks> /// <returns> /// The result of the authentication. /// </returns> public AuthenticationResult VerifyAuthentication(string returnUrl) { - Requires.NotNullOrEmpty(returnUrl, "returnUrl"); + // check for XSRF attack + string sessionId; + bool successful = this.ValidateRequestAgainstXsrfAttack(out sessionId); + if (!successful) { + return new AuthenticationResult( + isSuccessful: false, + provider: this.authenticationProvider.ProviderName, + providerUserId: null, + userName: null, + extraData: null); + } // Only OAuth2 requires the return url value for the verify authenticaiton step OAuth2Client oauth2Client = this.authenticationProvider as OAuth2Client; @@ -191,24 +213,69 @@ namespace DotNetOpenAuth.AspNet { uri = this.requestContext.Request.GetPublicFacingUrl(); } - AuthenticationResult result = oauth2Client.VerifyAuthentication(this.requestContext, uri); - if (!result.IsSuccessful) { - // if the result is a Failed result, creates a new Failed response which has providerName info. - result = new AuthenticationResult( - isSuccessful: false, - provider: this.authenticationProvider.ProviderName, - providerUserId: null, - userName: null, - extraData: null); - } + // attach the provider parameter so that we know which provider initiated + // the login when user is redirected back to this page + uri = uri.AttachQueryStringParameter(ProviderQueryStringName, this.authenticationProvider.ProviderName); + + // When we called RequestAuthentication(), we put the sessionId in the returnUrl query string. + // Hence, we need to put it in the VerifyAuthentication url again to please FB/Microsoft account providers. + uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId); - return result; + try { + AuthenticationResult result = oauth2Client.VerifyAuthentication(this.requestContext, uri); + if (!result.IsSuccessful) { + // if the result is a Failed result, creates a new Failed response which has providerName info. + result = new AuthenticationResult( + isSuccessful: false, + provider: this.authenticationProvider.ProviderName, + providerUserId: null, + userName: null, + extraData: null); + } + + return result; + } + catch (HttpException exception) { + return new AuthenticationResult(exception.GetBaseException(), this.authenticationProvider.ProviderName); + } } else { - return this.VerifyAuthentication(); + return this.authenticationProvider.VerifyAuthentication(this.requestContext); } } + /// <summary> + /// Validates the request against XSRF attack. + /// </summary> + /// <param name="sessionId">The session id embedded in the query string.</param> + /// <returns> + /// <c>true</c> if the request is safe. Otherwise, <c>false</c>. + /// </returns> + private bool ValidateRequestAgainstXsrfAttack(out string sessionId) { + // get the session id query string parameter + string queryStringSessionId = this.requestContext.Request.QueryString[SessionIdQueryStringName]; + + // verify that the query string value is a valid guid + Guid guid; + if (!Guid.TryParse(queryStringSessionId, out guid)) { + sessionId = null; + return false; + } + + // get the cookie id query string parameter + var cookie = this.requestContext.Request.Cookies[SessionIdCookieName]; + + bool successful = cookie != null && queryStringSessionId == cookie.Value; + + if (successful) { + // be a good citizen, clean up cookie when the authentication succeeds + this.requestContext.Response.Cookies.Remove(SessionIdCookieName); + } + + sessionId = queryStringSessionId; + return successful; + } + #endregion } }
\ No newline at end of file |