Skip to content
This repository has been archived by the owner on Mar 20, 2019. It is now read-only.

Commit

Permalink
Use cookie to store OAuth token and set it as default mechanism. Fix …
Browse files Browse the repository at this point in the history
…an issue in Facebook account with encoded return url. Update Twitter urls. Catch exception in VerifyAuthentication and return as Failed.
  • Loading branch information
Microsoft committed May 4, 2012
1 parent 8e6ea95 commit 36e1af6
Show file tree
Hide file tree
Showing 13 changed files with 557 additions and 42 deletions.
16 changes: 14 additions & 2 deletions src/DotNetOpenAuth.AspNet/AuthenticationResult.cs
Expand Up @@ -37,12 +37,24 @@ public AuthenticationResult(bool isSuccessful)
/// The exception.
/// </param>
public AuthenticationResult(Exception exception)
: this(isSuccessful: false) {
if (exception == null) {
: this(exception, provider: null) {
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationResult"/> class.
/// </summary>
/// <param name="exception">The exception.</param>
/// <param name="provider">The provider name.</param>
public AuthenticationResult(Exception exception, string provider)
: this(isSuccessful: false)
{
if (exception == null)
{
throw new ArgumentNullException("exception");
}

this.Error = exception;
this.Provider = provider;
}

/// <summary>
Expand Down
@@ -0,0 +1,96 @@
//-----------------------------------------------------------------------
// <copyright file="AuthenticationOnlyCookieOAuthTokenManager.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.AspNet.Clients {
using System;
using System.Text;
using System.Web;
using System.Web.Security;

/// <summary>
/// Stores OAuth tokens in the current request's cookie
/// </summary>
public class AuthenticationOnlyCookieOAuthTokenManager : IOAuthTokenManager {
/// <summary>
/// Key used for token cookie
/// </summary>
private const string TokenCookieKey = "OAuthTokenSecret";

/// <summary>
/// Primary request context.
/// </summary>
private readonly HttpContextBase primaryContext;

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
public AuthenticationOnlyCookieOAuthTokenManager() {
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
/// <param name="context">The current request context.</param>
public AuthenticationOnlyCookieOAuthTokenManager(HttpContextBase context) {
this.primaryContext = context;
}

/// <summary>
/// Gets the effective HttpContext object to use.
/// </summary>
private HttpContextBase Context {
get {
return this.primaryContext ?? new HttpContextWrapper(HttpContext.Current);
}
}

/// <summary>
/// Gets the token secret from the specified token.
/// </summary>
/// <param name="token">The token.</param>
/// <returns>
/// The token's secret
/// </returns>
public string GetTokenSecret(string token) {
HttpCookie cookie = this.Context.Request.Cookies[TokenCookieKey];
if (cookie == null || string.IsNullOrEmpty(cookie.Values[token])) {
return null;
}
byte[] cookieBytes = HttpServerUtility.UrlTokenDecode(cookie.Values[token]);
byte[] clearBytes = MachineKeyUtil.Unprotect(cookieBytes, TokenCookieKey, "Token:" + token);

string secret = Encoding.UTF8.GetString(clearBytes);
return secret;
}

/// <summary>
/// Replaces the request token with access token.
/// </summary>
/// <param name="requestToken">The request token.</param>
/// <param name="accessToken">The access token.</param>
/// <param name="accessTokenSecret">The access token secret.</param>
public void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) {
var cookie = new HttpCookie(TokenCookieKey) {
Value = string.Empty,
Expires = DateTime.UtcNow.AddDays(-5)
};
this.Context.Response.Cookies.Set(cookie);
}

/// <summary>
/// Stores the request token together with its secret.
/// </summary>
/// <param name="requestToken">The request token.</param>
/// <param name="requestTokenSecret">The request token secret.</param>
public void StoreRequestToken(string requestToken, string requestTokenSecret) {
var cookie = new HttpCookie(TokenCookieKey);
byte[] cookieBytes = Encoding.UTF8.GetBytes(requestTokenSecret);
var secretBytes = MachineKeyUtil.Protect(cookieBytes, TokenCookieKey, "Token:" + requestToken);
cookie.Values[requestToken] = HttpServerUtility.UrlTokenEncode(secretBytes);
this.Context.Response.Cookies.Set(cookie);
}
}
}
5 changes: 4 additions & 1 deletion src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
Expand Up @@ -48,6 +48,9 @@ public sealed class LinkedInClient : OAuthClient {
/// <summary>
/// Initializes a new instance of the <see cref="LinkedInClient"/> class.
/// </summary>
/// <remarks>
/// Tokens exchanged during the OAuth handshake are stored in cookies.
/// </remarks>
/// <param name="consumerKey">
/// The LinkedIn app's consumer key.
/// </param>
Expand All @@ -57,7 +60,7 @@ public sealed class LinkedInClient : OAuthClient {
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "We can't dispose the object because we still need it through the app lifetime.")]
public LinkedInClient(string consumerKey, string consumerSecret)
: base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { }
: this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { }

/// <summary>
/// Initializes a new instance of the <see cref="LinkedInClient"/> class.
Expand Down
11 changes: 7 additions & 4 deletions src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
Expand Up @@ -28,15 +28,15 @@ public class TwitterClient : OAuthClient {
public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription {
RequestTokenEndpoint =
new MessageReceivingEndpoint(
"https://twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/request_token",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint(
"https://twitter.com/oauth/authenticate",
"https://api.twitter.com/oauth/authenticate",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint =
new MessageReceivingEndpoint(
"https://twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/access_token",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
Expand All @@ -48,6 +48,9 @@ public class TwitterClient : OAuthClient {
/// <summary>
/// Initializes a new instance of the <see cref="TwitterClient"/> class with the specified consumer key and consumer secret.
/// </summary>
/// <remarks>
/// Tokens exchanged during the OAuth handshake are stored in cookies.
/// </remarks>
/// <param name="consumerKey">
/// The consumer key.
/// </param>
Expand All @@ -57,7 +60,7 @@ public class TwitterClient : OAuthClient {
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "We can't dispose the object because we still need it through the app lifetime.")]
public TwitterClient(string consumerKey, string consumerSecret)
: base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { }
: this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { }

/// <summary>
/// Initializes a new instance of the <see cref="TwitterClient"/> class.
Expand Down
31 changes: 28 additions & 3 deletions src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs
Expand Up @@ -76,7 +76,10 @@ public FacebookClient(string appId, string appSecret)
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgs(
new Dictionary<string, string> { { "client_id", this.appId }, { "redirect_uri", returnUrl.AbsoluteUri }, });
new Dictionary<string, string> {
{ "client_id", this.appId },
{ "redirect_uri", returnUrl.AbsoluteUri }
});
return builder.Uri;
}

Expand Down Expand Up @@ -127,7 +130,7 @@ public FacebookClient(string appId, string appSecret)
builder.AppendQueryArgs(
new Dictionary<string, string> {
{ "client_id", this.appId },
{ "redirect_uri", returnUrl.AbsoluteUri },
{ "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) },
{ "client_secret", this.appSecret },
{ "code", authorizationCode },
});
Expand All @@ -143,6 +146,28 @@ public FacebookClient(string appId, string appSecret)
}
}

/// <summary>
/// Converts any % encoded values in the URL to uppercase.
/// </summary>
/// <param name="url">The URL string to normalize</param>
/// <returns>The normalized url</returns>
/// <example>NormalizeHexEncoding("Login.aspx?ReturnUrl=%2fAccount%2fManage.aspx") returns "Login.aspx?ReturnUrl=%2FAccount%2FManage.aspx"</example>
/// <remarks>
/// There is an issue in Facebook whereby it will rejects the redirect_uri value if
/// the url contains lowercase % encoded values.
/// </remarks>
private static string NormalizeHexEncoding(string url) {
var chars = url.ToCharArray();
for (int i = 0; i < chars.Length - 2; i++) {
if (chars[i] == '%') {
chars[i + 1] = char.ToUpperInvariant(chars[i + 1]);
chars[i + 2] = char.ToUpperInvariant(chars[i + 2]);
i += 2;
}
}
return new string(chars);
}

#endregion
}
}
}
10 changes: 1 addition & 9 deletions src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs
Expand Up @@ -21,11 +21,6 @@ public abstract class OAuth2Client : IAuthenticationClient {
/// </summary>
private readonly string providerName;

/// <summary>
/// The return url.
/// </summary>
private Uri returnUrl;

#endregion

#region Constructors and Destructors
Expand Down Expand Up @@ -71,8 +66,6 @@ public abstract class OAuth2Client : IAuthenticationClient {
Requires.NotNull(context, "context");
Requires.NotNull(returnUrl, "returnUrl");

this.returnUrl = returnUrl;

string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri;
context.Response.Redirect(redirectUrl, endResponse: true);
}
Expand All @@ -87,8 +80,7 @@ public abstract class OAuth2Client : IAuthenticationClient {
/// An instance of <see cref="AuthenticationResult"/> containing authentication result.
/// </returns>
public AuthenticationResult VerifyAuthentication(HttpContextBase context) {
Requires.NotNull(this.returnUrl, "this.returnUrl");
return VerifyAuthentication(context, this.returnUrl);
throw new InvalidOperationException(WebResources.OAuthRequireReturnUrl);
}

/// <summary>
Expand Down
Expand Up @@ -37,8 +37,7 @@ public GoogleOpenIdClient()
if (fetchResponse != null) {
var extraData = new Dictionary<string, string>();
extraData.AddItemIfNotEmpty("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.AddItemIfNotEmpty(
"country", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country));
extraData.AddItemIfNotEmpty("country", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country));
extraData.AddItemIfNotEmpty("firstName", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.First));
extraData.AddItemIfNotEmpty("lastName", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Last));

Expand Down Expand Up @@ -67,4 +66,4 @@ public GoogleOpenIdClient()

#endregion
}
}
}
4 changes: 4 additions & 0 deletions src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
Expand Up @@ -42,6 +42,9 @@
<ItemGroup>
<Compile Include="AuthenticationResult.cs" />
<Compile Include="Clients\DictionaryExtensions.cs" />
<Compile Include="Clients\OAuth\AuthenticationOnlyCookieOAuthTokenManager.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Clients\OAuth\IOAuthTokenManager.cs" />
<Compile Include="Clients\OAuth\SimpleConsumerTokenManager.cs" />
<Compile Include="IAuthenticationClient.cs" />
Expand All @@ -61,6 +64,7 @@
<Compile Include="Clients\OpenID\GoogleOpenIdClient.cs" />
<Compile Include="Clients\OpenID\OpenIdClient.cs" />
<Compile Include="Clients\OpenID\YahooOpenIdClient.cs" />
<Compile Include="MachineKeyUtil.cs" />
<Compile Include="UriHelper.cs" />
<Compile Include="IOpenAuthDataProvider.cs" />
<Compile Include="OpenAuthAuthenticationTicketHelper.cs" />
Expand Down

0 comments on commit 36e1af6

Please sign in to comment.