Как разрешить пользователям входить на мой сайт с помощью SoundCloud

Я хочу, чтобы пользователи аутентифицировались через SoundCloud для моего проекта ASP.NET MVC 4. Поскольку .NET SDK не существует, я написал собственный OAuth2Client для обработки аутентификации. После добавления клиента в мой AuthConfig.cs он соответственно появился как вариант входа в систему. Проблема в том, что когда я нажимаю кнопку для входа, она всегда возвращается

Login Failure.

Unsuccessful login with service.

даже не попросив меня войти в SoundCloud. В чем проблема? Я реализовал очень похожий клиент для GitHub, и он работал без проблем.

Вот мой клиент:

 public class SoundCloudOAuth2Client : OAuth2Client
 {
     private const string ENDUSERAUTHLINK = "https://soundcloud.com/connect";
     private const string TOKENLINK = "https://api.soundcloud.com/oauth2/token";
     private readonly string _clientID;
     private readonly string _clientSecret;

     public SoundCloudOAuth2Client(string clientID, string clientSecret) : base("SoundCloud")
     {
         if (string.IsNullOrWhiteSpace(clientID)) {
                throw new ArgumentNullException("clientID");
         }

         if (string.IsNullOrWhiteSpace(clientSecret)) {
                throw new ArgumentNullException("clientSecret");
         }

         _clientID = clientID;
         _clientSecret = clientSecret;
     }

     protected override Uri GetServiceLoginUrl(Uri returnUrl)
     {
         StringBuilder serviceUrl = new StringBuilder();
         serviceUrl.Append(ENDUSERAUTHLINK);
         serviceUrl.AppendFormat("?client_id={0}", _clientID);
         serviceUrl.AppendFormat("&response_type={0}", "code");
         serviceUrl.AppendFormat("&scope={0}", "non-expiring");
         serviceUrl.AppendFormat("&redirect_uri={0}", System.Uri.EscapeDataString(returnUrl.ToString()));

         return new Uri(serviceUrl.ToString());
     }

     public override void RequestAuthentication(HttpContextBase context, Uri returnUrl)
     {
         base.RequestAuthentication(context, returnUrl);
     }

     protected override IDictionary<string, string> GetUserData(string accessToken)
     {
         IDictionary<String, String> extraData = new Dictionary<String, String>();

         var webRequest = (HttpWebRequest)WebRequest.Create("https://api.soundcloud.com/me.json?oauth_token=" + accessToken);
         webRequest.Method = "GET";
         string response = "";
         using (HttpWebResponse webResponse = HttpWebResponse)webRequest.GetResponse())
         {
             using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
             {
                 response = reader.ReadToEnd();
             }
         }

         var json = JObject.Parse(response);
         string id = (string)json["id"];
         string username = (string)json["username"];
         string permalinkUrl = (string)json["permalink_url"];

         extraData = new Dictionary<String, String>
         {
             {"SCAccessToken", accessToken},
             {"username", username}, 
             {"permalinkUrl", permalinkUrl}, 
             {"id", id}                                           
         };

         return extraData;
     }

     protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
     {
         StringBuilder postData = new StringBuilder();
         postData.AppendFormat("client_id={0}", this._clientID);
         postData.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(returnUrl.ToString()));
         postData.AppendFormat("&client_secret={0}", this._clientSecret);
         postData.AppendFormat("&grant_type={0}", "authorization_code");
         postData.AppendFormat("&code={0}", authorizationCode);

         string response = "";
         string accessToken = "";

         var webRequest = (HttpWebRequest)WebRequest.Create(TOKENLINK);    
         webRequest.Method = "POST";
         webRequest.ContentType = "application/x-www-form-urlencoded";

         using (Stream s = webRequest.GetRequestStream())
         {
             using (StreamWriter sw = new StreamWriter(s))
                    sw.Write(postData.ToString());
         }

         using (WebResponse webResponse = webRequest.GetResponse())
         {
             using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
             {
                 response = reader.ReadToEnd();
             }
         }

         var json = JObject.Parse(response);
         accessToken = (string)json["access_token"];

         return accessToken;
     }

     public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
     {    
         string code = context.Request.QueryString["code"];  
         string u = context.Request.Url.ToString();

         if (string.IsNullOrEmpty(code))
         {
             return AuthenticationResult.Failed;
         }

         string accessToken = this.QueryAccessToken(returnPageUrl, code);
         if (accessToken == null)
         {
             return AuthenticationResult.Failed;
         }

         IDictionary<string, string> userData = this.GetUserData(accessToken);
         if (userData == null)
         {
             return AuthenticationResult.Failed;
         }

         string id = userData["id"];
         string name;

         if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name))
         {
             name = id;
         }

         return new AuthenticationResult(
             isSuccessful: true, provider: "SoundCloud", providerUserId: id, userName: name, extraData: userData);
     }
 }

и AuthConfig.cs:

 public static void RegisterAuth()
 {
     OAuthWebSecurity.RegisterClient(new SoundCloudOAuth2Client(
         clientID: MyValues.MyClientID,
         clientSecret: MyValues.MyClientSECRET), 
         displayName: "SoundCloud",
         extraData: null);

     OAuthWebSecurity.RegisterClient(new GitHubOAuth2Client(
         appId: MyValues.GITHUBAPPID,
         appSecret: MyValues.GITHUBAPPSECRET), "GitHub", null);

     OAuthWebSecurity.RegisterGoogleClient();
     OAuthWebSecurity.RegisterYahooClient();
 }

person MikeSmithDev    schedule 20.02.2013    source источник


Ответы (1)


Необходимо решить несколько проблем, начиная с первой запущенной функции: GetServiceLoginUrl(Uri returnUrl)

returnUrl, который создается автоматически, содержит амперсанды, что не нравится SoundCloud. Вам нужно удалить амперсанд и убедиться, что «URI перенаправления для аутентификации» в вашей учетной записи SoundCloud точно соответствует тому, что отправляется (строка запроса и все остальное). Вот пример того, что по умолчанию отправлялось как returnURL:

https://localhost:44301/Account/ExternalLoginCallback?__provider__=SoundCloud&__sid__=blahblahyoursid

Первым шагом было удаление значения &__sid__. Вы можете удалить значение sid и передать его как параметр state на тот случай, если оно вам когда-нибудь понадобится. Новая функция выглядит так:

protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
    StringBuilder serviceUrl = new StringBuilder();
    string sid = String.Empty;
    if (returnUrl.ToString().Contains("__sid__"))
    {
        int index = returnUrl.ToString().IndexOf("__sid__") + 8;
        int len = returnUrl.ToString().Length;
        sid = returnUrl.ToString().Substring(index, len - index-1);
    }

    string redirectUri = returnUrl.ToString().Contains('&') ? 
    returnUrl.ToString().Substring(0,returnUrl.ToString().IndexOf("&")) : 
    returnUrl.ToString();
    serviceUrl.Append(ENDUSERAUTHLINK);
    serviceUrl.AppendFormat("?client_id={0}", _clientID);
    serviceUrl.AppendFormat("&response_type={0}", "code");
    serviceUrl.AppendFormat("&scope={0}", "non-expiring");
    serviceUrl.AppendFormat("&state={0}", sid);
    serviceUrl.AppendFormat("&redirect_uri={0}", System.Uri.EscapeDataString(redirectUri));

    return new Uri(serviceUrl.ToString());
}

Это решает часть проблемы. URI перенаправления в SoundlCoud теперь просто https://localhost:44301/Account/ExternalLoginCallback?__provider__=SoundCloud). Но попытка аутентификации все равно вернет false. Следующая проблема, которую необходимо решить, находится в AccountController.cs, а именно:

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)

потому что в первой строке он пытается вернуть:

AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

и это не работает для моего пользовательского OAuth2Client, так как VerifyAuthentication принимает другие параметры. Исправьте это, определив, является ли это клиентом SoundCloud, а затем используйте пользовательскую VerifyAuthentication:

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    AuthenticationResult result;
    var context = this.HttpContext;
    string p = Tools.GetProviderNameFromQueryString(context.Request.QueryString);

    if (!String.IsNullOrEmpty(p) && p.ToLower() == "soundcloud")
    {
        result = new SoundCloudOAuth2Client(
                clientID: MyValues.SCCLIENTID,
                clientSecret: MyValues.SCCLIENTSECRET).VerifyAuthentication(this.HttpContext, new Uri(String.Format("{0}/Account/ExternalLoginCallback?__provider__=SoundCloud", context.Request.Url.GetLeftPart(UriPartial.Authority).ToString())));
    }
    else
    {
        result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
    }

куда

public static string GetProviderNameFromQueryString(NameValueCollection queryString)
{
    var result = queryString["__provider__"];
    ///commented out stuff
    return result;
}

После этого все работает нормально, и вы можете успешно пройти аутентификацию. Вы можете настроить GetUserData для получения любых данных SoundCloud, которые вы хотите сохранить, а затем сохранить их в свой профиль пользователя или связанную таблицу. Ключевой частью является то, что SCAccessToken потому что это то, что вам понадобится в будущем для загрузки в их учетную запись.

person MikeSmithDev    schedule 20.02.2013
comment
есть идеи, как заставить SoundCloud Authentication работать с Owin? - person galdin; 12.12.2014
comment
@gldraphael Нет, извините. Все мои недавние использования аутентификации Sound Cloud были в node.js. Хотя я бы подумал (надеялся), что, поскольку я написал этот пост, будет пакет на nuget или что-то в этом роде. - person MikeSmithDev; 12.12.2014