Last active
December 18, 2015 09:29
-
-
Save Maarten88/5761358 to your computer and use it in GitHub Desktop.
Amazon OAuth2 Client for DotNetOpenAuth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
// | |
// GET: /Account/ExternalLoginCallback | |
[AllowAnonymous] | |
public ActionResult ExternalLoginCallback(string returnUrl) | |
{ | |
// Hack around DotNetOpenAuth and OAuth providers using the returnUrl for things that are in the state parameter (Amazon, Google) | |
// Our provider passed the querystring part of the replay address into the state field | |
// On return we parse that state parameter and put the arguments back where DotNetOpenAuth expects them, than redirect back to ourselves | |
if (!String.IsNullOrEmpty(Request.QueryString["state"])) | |
{ | |
var routeValues = this.RouteData.Values; | |
foreach (string key in Request.QueryString.AllKeys) | |
if (key.ToLower() == "state") | |
{ | |
NameValueCollection stateCollection = HttpUtility.ParseQueryString(Request.QueryString["state"]); | |
foreach (string stateKey in stateCollection.AllKeys) | |
routeValues.Add(stateKey, stateCollection[stateKey]); | |
} | |
else | |
{ | |
routeValues.Add(key, Request.QueryString[key]); | |
} | |
return RedirectToRoute(routeValues); | |
} | |
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl })); | |
if (!result.IsSuccessful) | |
{ | |
return RedirectToAction("ExternalLoginFailure"); | |
} | |
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false)) | |
{ | |
return RedirectToLocal(returnUrl); | |
} | |
if (User.Identity.IsAuthenticated) | |
{ | |
// If the current user is logged in add the new account | |
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name); | |
return RedirectToLocal(returnUrl); | |
} | |
else | |
{ | |
// User is new, ask for their desired membership name | |
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId); | |
ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName; | |
ViewBag.ReturnUrl = returnUrl; | |
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData }); | |
} | |
} | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ------------------------------------------------- | |
* Amazon Login Client for DotNetOpenAuth | |
* Maarten Sikkema, Macaw | |
* MIT Licence (http://opensource.org/licenses/MIT) | |
* | |
* ------------------------------------------------- */ | |
namespace DotNetOpenAuth.AspNet.Clients | |
{ | |
using DotNetOpenAuth.Messaging; | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Globalization; | |
using System.IO; | |
using System.Net; | |
using System.Runtime.Serialization; | |
using System.Runtime.Serialization.Json; | |
using System.Text; | |
using System.Web; | |
using Validation; | |
/// <summary> | |
/// The amazon client. | |
/// </summary> | |
public sealed class AmazonClient : OAuth2Client | |
{ | |
#region Constants and Fields | |
/// <summary> | |
/// The authorization endpoint. | |
/// </summary> | |
private const string AuthorizationEndpoint = "https://www.amazon.com/ap/oa"; | |
/// <summary> | |
/// The token endpoint. | |
/// </summary> | |
private const string TokenEndpoint = "https://api.amazon.com/auth/o2/token"; | |
/// <summary> | |
/// The _app id. | |
/// </summary> | |
private readonly string clientId; | |
/// <summary> | |
/// The _app secret. | |
/// </summary> | |
private readonly string clientSecret; | |
/// <summary> | |
/// The scope. | |
/// </summary> | |
private readonly string[] scope; | |
#endregion | |
#region Constructors and Destructors | |
/// <summary> | |
/// Initializes a new instance of the <see cref="FacebookClient"/> class | |
/// with "email" as the scope. | |
/// </summary> | |
/// <param name="appId"> | |
/// The app id. | |
/// </param> | |
/// <param name="appSecret"> | |
/// The app secret. | |
/// </param> | |
public AmazonClient(string clientId, string clientSecret) | |
: this(clientId, clientSecret, "profile") | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="FacebookClient"/> class. | |
/// </summary> | |
/// <param name="clientId"> | |
/// The app id. | |
/// </param> | |
/// <param name="clientSecret"> | |
/// The app secret. | |
/// </param> | |
/// <param name="scope"> | |
/// The scope of authorization to request when authenticating with Facebook. The default is "profile". | |
/// </param> | |
public AmazonClient(string clientId, string clientSecret, params string[] scope) | |
: base("amazon") | |
{ | |
Requires.NotNullOrEmpty(clientId, "clientId"); | |
Requires.NotNullOrEmpty(clientSecret, "clientSecret"); | |
Requires.NotNullOrEmpty(scope, "scope"); | |
this.clientId = clientId; | |
this.clientSecret = clientSecret; | |
this.scope = scope; | |
} | |
#endregion | |
#region Methods | |
/// <summary> | |
/// The get service login url. | |
/// </summary> | |
/// <param name="returnUrl"> | |
/// The return url. | |
/// </param> | |
/// <returns>An absolute URI.</returns> | |
protected override Uri GetServiceLoginUrl(Uri returnUrl) | |
{ | |
var builder = new UriBuilder(AuthorizationEndpoint); | |
builder.AppendQueryArgument("client_id", this.clientId); | |
// We must circumvert DotNetOpenAuth adding its stuff to the returnUrl to the Redirect Uri... The OAuth spec does not allow it and Amazon will reject it | |
builder.AppendQueryArgument("redirect_uri", returnUrl.GetLeftPart(UriPartial.Path)); | |
// we must send the extra stuff in the state parameter. On return we'll add it back and redirect to make DotNetOpenAuth happy | |
builder.AppendQueryArgument("state", returnUrl.Query); | |
builder.AppendQueryArgument("response_type", "code"); | |
builder.AppendQueryArgument("scope", string.Join(" ", this.scope)); | |
return builder.Uri; | |
} | |
/// <summary> | |
/// The get user data. | |
/// </summary> | |
/// <param name="accessToken"> | |
/// The access token. | |
/// </param> | |
/// <returns>A dictionary of profile data.</returns> | |
protected override IDictionary<string, string> GetUserData(string accessToken) | |
// protected override NameValueCollection GetUserData(string accessToken) | |
{ | |
AmazonProfileData userProfile; | |
var request = WebRequest.Create("https://api.amazon.com/user/profile?access_token=" + HttpUtility.UrlEncode(accessToken)); | |
request.Headers.Add(HttpRequestHeader.Authorization, string.Format(CultureInfo.InvariantCulture, "Bearer {0}", accessToken)); | |
request.PreAuthenticate = true; | |
using (var response = request.GetResponse()) | |
{ | |
using (var responseStream = response.GetResponseStream()) | |
{ | |
userProfile = JsonHelper.Deserialize<AmazonProfileData>(responseStream); | |
} | |
} | |
// this dictionary must contains | |
var userData = new Dictionary<string, string>(); | |
// var userData = new NameValueCollection(); | |
if (!string.IsNullOrEmpty(userProfile.CustomerId)) userData.Add("id", userProfile.CustomerId); | |
if (!string.IsNullOrEmpty(userProfile.PrimaryEmail)) userData.Add("email", userProfile.PrimaryEmail); | |
if (!string.IsNullOrEmpty(userProfile.Name)) userData.Add("name", userProfile.Name); | |
if (!string.IsNullOrEmpty(userProfile.PostalCode)) userData.Add("postal_code", userProfile.PostalCode); | |
return userData; | |
} | |
/// <summary> | |
/// Obtains an access token given an authorization code and callback URL. | |
/// </summary> | |
/// <remarks> | |
/// Amazon uses the state parameter and does not allow adding data to the reply address, as per the OAuth 2.0 spec | |
/// Add the following code in the top to ExternalLoginCallback to translate the state parameters back where FotNetOpenAuth expects them | |
/// | |
/// if (!String.IsNullOrEmpty(Request.QueryString["state"])) | |
/// { | |
/// var routeValues = this.RouteData.Values; | |
/// foreach (string key in Request.QueryString.AllKeys) | |
/// if (key.ToLower() == "state") | |
/// { | |
/// NameValueCollection stateCollection = HttpUtility.ParseQueryString(Request.QueryString["state"]); | |
/// foreach (string stateKey in stateCollection.AllKeys) | |
/// routeValues.Add(stateKey, stateCollection[stateKey]); | |
/// } | |
/// else | |
/// { | |
/// routeValues.Add(key, Request.QueryString[key]); | |
/// } | |
/// return RedirectToRoute(routeValues); | |
/// } | |
/// | |
/// </remarks> | |
/// <param name="returnUrl"> | |
/// The return url. | |
/// </param> | |
/// <param name="authorizationCode"> | |
/// The authorization code. | |
/// </param> | |
/// <returns> | |
/// The access token. | |
/// </returns> | |
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) | |
{ | |
var sb = new StringBuilder(); | |
sb.Append("grant_type=authorization_code"); | |
sb.Append("&code=").Append(HttpUtility.UrlEncode(authorizationCode)); | |
// we need to circumvert DotNetOpenAuth adding its stuff to the returnUrl to the Redirect Uri... Amazon will reject it | |
sb.Append("&redirect_uri=").Append(HttpUtility.UrlEncode(returnUrl.GetLeftPart(UriPartial.Path))); | |
sb.Append("&client_id=").Append(HttpUtility.UrlEncode(this.clientId)); | |
sb.Append("&client_secret=").Append(HttpUtility.UrlEncode(this.clientSecret)); | |
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(TokenEndpoint); | |
request.Method = "POST"; | |
request.ContentType = "application/x-www-form-urlencoded"; | |
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString()); | |
request.ContentLength = bytes.Length; | |
using (Stream requestStream = request.GetRequestStream()) | |
{ | |
requestStream.Write(bytes, 0, bytes.Length); | |
using (WebResponse response = request.GetResponse()) | |
{ | |
using (Stream stream = response.GetResponseStream()) | |
{ | |
AmazonTokenResponse result = JsonHelper.Deserialize<AmazonTokenResponse>(stream); | |
return result.AccessToken; | |
} | |
} | |
} | |
} | |
#endregion | |
/// <summary> | |
/// The json helper. | |
/// </summary> | |
internal static class JsonHelper | |
{ | |
#region Public Methods and Operators | |
/// <summary> | |
/// The deserialize. | |
/// </summary> | |
/// <param name="stream"> | |
/// The stream. | |
/// </param> | |
/// <typeparam name="T">The type of the value to deserialize.</typeparam> | |
/// <returns> | |
/// The deserialized value. | |
/// </returns> | |
public static T Deserialize<T>(Stream stream) where T : class | |
{ | |
Requires.NotNull(stream, "stream"); | |
var serializer = new DataContractJsonSerializer(typeof(T)); | |
return (T)serializer.ReadObject(stream); | |
} | |
#endregion | |
} | |
} | |
/// <summary> | |
/// Contains data of a Amazon user. | |
/// </summary> | |
/// <remarks> | |
/// Technically, this class doesn't need to be public, but because we want to make it serializable in medium trust, it has to be public. | |
/// </remarks> | |
[DataContract] | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amazon", Justification = "Brand name")] | |
public class AmazonProfileData | |
{ | |
#region Public Properties | |
/// <summary> | |
/// Gets or sets the email. | |
/// </summary> | |
/// <value> The email. </value> | |
[DataMember(Name = "email")] | |
public string PrimaryEmail { get; set; } | |
/// <summary> | |
/// Gets or sets the id. | |
/// </summary> | |
/// <value> The id. </value> | |
[DataMember(Name = "user_id")] | |
public string CustomerId { get; set; } | |
/// <summary> | |
/// Gets or sets the name. | |
/// </summary> | |
/// <value> The name. </value> | |
[DataMember(Name = "name")] | |
public string Name { get; set; } | |
/// <summary> | |
/// Gets or sets the postal code. | |
/// </summary> | |
/// <value> The name. </value> | |
[DataMember(Name = "postal_code")] | |
public string PostalCode { get; set; } | |
#endregion | |
} | |
[DataContract] | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amazon", Justification = "Brand name")] | |
public class AmazonTokenResponse | |
{ | |
[DataMember(Name = "access_token")] | |
public string AccessToken { get; set; } | |
[DataMember(Name = "token_type")] | |
public string TokenType { get; set; } | |
[DataMember(Name = "expires_in")] | |
public long ExpiresIn { get; set; } | |
[DataMember(Name = "refresh_token")] | |
public string RefreshToken { get; set; } | |
[DataMember(Name = "scope")] | |
public string Scope { get; set; } | |
[DataMember(Name = "error")] | |
public string Error { get; set; } | |
[DataMember(Name = "error_description")] | |
public string ErrorDescription { get; set; } | |
[DataMember(Name = "error_uri")] | |
public string ErrorUri { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
OAuthWebSecurity.RegisterClient(new AmazonClient( | |
clientId: "amzn1.application-oa2-client.123abc", | |
clientSecret: "123abc"), "Amazon", null); | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment