OAuth-аутентификация через ВКонтакте

В одном из проектов появилась задача обеспечить вход пользователей на сайт через социальные сети. Начало было положено с помощью модуля DotNetOpenAuth extensions for ASP.NET (WebPages). В результате появилась возможность использовать такие сети:

  • Facebook
  • Google+
  • Live.com
  • LinkedId
  • Twitter
На борту имелся клиент еще и для Yahoo, но в связи с его малой популярностью в Рунете решили его не подключать. А вот для популярных в Рунете сетей ВКонтакте и Одноклассники встроенной поддержки нет. Решил сделать это самостоятельно, тем более что, на первый взгляд, задача казалась не сложной.

Как оказалось - только на первый взгляд. У каждой социальной сети есть свои нюансы. Так как по натуре я не жадный, кроме того - хочу чтобы вы подсказали мне ошибки, вот код для ВКонтакте:

https://gist.github.com/4556009

Примечание: Я убрал встроенный код и добавил ссылку на GitHub, так как тут код показывается плохо.

Дополнение: Коллега прислал ссылочку на готовые модули для ВКонтакте и Одноклассники:
http://promorepublic.github.io/oauth/

Чтобы подключить этот код надо:

  1. Добавить его в проект (оба файла).
  2. Зарегистрировать приложение на сайте ВКонтакте и получить Id приложения и секретный ключ.
  3. Зарегистрировать провайдер где-то в Global.asax.cs таким вот вызовом:
    OAuthWebSecurity.RegisterClient(new VKontakteOAuthProvider("*****", "********"), "ВКонтакте", new Dictionary());

Коментарі

Kalinets' family каже…
код справа обрезанный :)
Unknown каже…
А скроллбар внизу для кого?
Надо конечно поправить темплиту...
Ivan каже…
И как этот клиент подключить к проэкту DotNetOpenAuth ?

Unknown каже…
Обновил код и саму заметку. Добавил информацию об использовании.
Ivan каже…
Как его зарегистрировать в 'OpenAuth.AuthenticationClients.Add ????

Если Можно
Unknown каже…
Я же написал в самом посте. Последняя строчка поста:
OAuthWebSecurity.RegisterClient(new VKontakteOAuthProvider("*****", "********"), "ВКонтакте", new Dictionary());
Ivan каже…
Все сделал, работает но выдет ошибку при вызове

OpenAuth.Login(authResult.Provider,...

ошибка- To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".

В инете куча мусора по этому поводу. У вас есть идеи?
Я использую авторизацию через AspNetSQL и мне нада прилепить пользователей через OAuth, к слову они хорошо лепятся и записываются всбазу но эта ошибка вылазить после попытки залогинить нового узера. Мои сайты (bazarodessa.com, bazarkiev.com, ukrainebazar.com) работают пока через AspNetSQL авторизацию, и подключить авторизацию через DotNetOAuth непроблема, но дополнительные провайдеры - это гемор (хотя ваш код реально помог для VK) но осталась одна ступенька и тогда можно будет подключать всех.
Unknown каже…
Иван,

Рассмотрите вариант не реализовывать MembershipProvider самостоятельно, а воспользоваться реализацией, которую предоставляет Microsoft в nuget-пакете Microsoft.AspNet.WebPages.WebData. Там их собственно 2: ExtendedMembershipProvider и SimpleMembershipProvider (унаследован от предыдущего).

С ними все должно работать.
Ivan каже…
Перевел сайт Ukrainebazar.com на использование встроенных в DotNet сервисов. Но с дополнительными всё ещё проблема. Может знаете как всетаки зарегестрировать провайдера через 'OpenAuth.AuthenticationClients.Add(???)' или хотябы где найти документацию или пример по использованию именно этого метода. Спасибо
Ivan каже…
Private Function getVKontakte() As OAuthClients.VKontakteOAuthProvider
Return New OAuthClients.VKontakteOAuthProvider("12345", "dsfvdgdfgfd")
End Function
*****************************
OpenAuth.AuthenticationClients.Add("VKontakte", AddressOf getVKontakte)


Unknown каже…
Интересно выглядит код на бейсике. Хоть и не так страшно как в VB 6.
Ivan каже…
Он практически идентичен С#
И главное работает.
Unknown каже…
А чем оправдано его использование? Для C# и примеров больше и синтаксис лаконичнее.
Ivan каже…
Дело в том что я к нему привык, а любой пример н С# я спокойно читаю и переделываю на VB
Анонім каже…
Спасибо за код, помог - переписываю самопальную реализацию на майкрософто-опенсурсную..

Вопрос - почему решили отказать от проверки state?Просто не стали заморчиваться?

Спасибо
Unknown каже…
Пожалуйста :)
Вот если бы еще кто-то в ответ поделился кодом для Одноклассников. Что-то у меня никак не получается сгенерировать сообщений, которое их сервер признает...
Unknown каже…
kaatula, извините вопрос не заметил. На самом деле, для реализации руководствовался описанием на сайте ВКонтакте, а RFC не читал. Кроме того, реализовывал только абсолютно необходимые вещи.
Если Вы расширили реализацию поддержкой state, может поделитесь?
Ivan каже…
Это Одноклассники реально работает на Ukrainebazar.com

Imports System
Imports System.Collections.Generic
Imports System.Diagnostics.CodeAnalysis
Imports System.Net
Imports System.Web
Imports System.ComponentModel
Imports System.Linq
Imports System.IO
Imports System.Text

Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Json

Imports DotNetOpenAuth
Imports DotNetOpenAuth.AspNet.Clients
Imports DotNetOpenAuth.Messaging
Imports Microsoft.AspNet.Membership.OpenAuth
Imports OAuthLib


Namespace OAuthClients

Public Class OdnoklassnikiOAuthProvider
Inherits OAuth2Client

Private appId As String
Private appSecret As String

Public Sub New(ByVal appId As String, ByVal appSecret As String)
MyBase.New("odnoklassniki")
Me.appId = appId
Me.appSecret = appSecret
End Sub

'http://www.odnoklassniki.ru/oauth/authorize
'client_id - идентификатор приложения
'response_type - на данный момент поддерживается только тип ответа «code»
'redirect_uri - URI для переадресации пользователя после аутентификации с дополнительным параметром
'scope - перечень прав доступа, разделенных символом «;»:
' VALUABLE ACCESS - доступ к методам API, за исключением приведенных ниже (методы users.getLoggedInUser и users.getCurrentUser вызываются без параметра scope)
' SET STATUS
' PHOTO CONTENT
Protected Overrides Function GetServiceLoginUrl(returnUrl As Uri) As Uri
If returnUrl = Nothing Then Throw New ArgumentNullException("returnUrl")
Dim builder As UriBuilder = New UriBuilder("http://www.odnoklassniki.ru/oauth/authorize")
Dim args As New Dictionary(Of String, String)
args.Add("client_id", appId)
args.Add("response_type", "code")
args.Add("redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri))
builder.AppendQueryArgs(args)
Return builder.Uri
End Function

'http://api.odnoklassniki.ru/fb.do
'GET http://api.odnoklassniki.ru/fb.do?
'method=users.getCurrentUser&
'access_token={access_token}&
'application_key={public_key}&
'sig={sign}
' sign — трахнутая на все байты md5-подпись
'sign=hex_md5('application_key={public_key}method=users.getCurrentUser'+hex_md5({access_token}+{secret_key}))

'1.Методы API нужно вызывать с помощью параметра access_token вместо session_key
'2.Каждый параметр подписи запроса sig вычисляется немного иным способом, чем обычно:
' sig = md5( request_params_composed_string+ md5(access_token + application_secret_key) )
' Не включайте маркер доступа access_token в request_params_composed_string

...
Ivan каже…
...
Protected Overrides Function GetUserData(accessToken As String) As IDictionary(Of String, String)
If accessToken Is String.Empty Then Throw New ArgumentNullException("accessToken")
Dim data As OdnoklassnikiTokenResponse = HttpContext.Current.Session("OdnoklassnikiOAuthProvider.Token")
If data Is Nothing Or data.AccessToken <> accessToken Then
Return Nothing
End If
Dim res As New Dictionary(Of String, String)
Dim builder As UriBuilder = New UriBuilder("http://api.odnoklassniki.ru/fb.do")
Dim args As New Dictionary(Of String, String)

args.Add("application_key", "YCGYVGVYDFVH HDA")
args.Add("method", "users.getCurrentUser")
Dim sig As String = Helpers.GetSig(Helpers.GetMD5(accessToken & Me.appSecret), args)
args.Add("access_token", accessToken)
args.Add("sig", sig)
builder.AppendQueryArgs(args)
'send request
Dim httpWebRequest As HttpWebRequest = WebRequest.Create(builder.Uri)
httpWebRequest.Method = "GET"
httpWebRequest.AllowAutoRedirect = True
Dim response As String = ""
Try
Dim httpWebResponse As HttpWebResponse = httpWebRequest.GetResponse

Using sr As IO.Stream = httpWebResponse.GetResponseStream
'test response
'Dim str As String = New StreamReader(sr).ReadToEnd
'end test
Dim serializer As New DataContractJsonSerializer(GetType(OdnoklassnikiDataItem))
Dim item As OdnoklassnikiDataItem = serializer.ReadObject(sr)

res.Add("id", "odn_" & item.UserId.ToString())
res.AddItemIfNotEmpty("username", (item.Username))
res.AddItemIfNotEmpty("name", (((item.FirstName & "") + " " + (item.LastName & "")).Trim()))
res.AddItemIfNotEmpty("birthday", item.Birthdate)
res.AddItemIfNotEmpty("gender", item.Sex)
End Using

Catch ex As Exception

End Try
Return res
End Function

'http://api.odnoklassniki.ru/oauth/token.do
'code - код авторизации, полученный в ответном адресе URL пользователя
'redirect_uri - тот же URI для переадресации, который был указан при первом вызове
'grant_type - _на данный момент поддерживается только код авторизации authorization_code
'client_id - идентификатор приложения
'client_secret - секретный ключ приложения
Protected Overrides Function QueryAccessToken(returnUrl As Uri, authorizationCode As String) As String
Dim url As String = "http://api.odnoklassniki.ru/oauth/token.do"
Dim par As New Dictionary(Of String, String)
par.Add("client_id", Me.appId)
par.Add("client_secret", Me.appSecret)
par.Add("grant_type", "authorization_code")
par.Add("code", authorizationCode)
par.Add("redirect_uri", Helpers.UrlEncode(returnUrl.AbsoluteUri))

Dim parameters As String = getParametersString(par).Remove(0, 1)

Return sendPostRequest(url, parameters)

End Function

...
Ivan каже…
...
Private Function NormalizeHexEncoding(ByVal url As String) As String
Dim chArray As Char() = url.ToCharArray
Dim i As Integer = 0
For i = 0 To i < chArray.Length - 2
If chArray(i) = "%" Then
chArray(i + 1) = Char.ToUpperInvariant(chArray(i + 1))
chArray(i + 2) = Char.ToUpperInvariant(chArray(i + 2))
i += 2
End If
Next
Return New String(chArray)
End Function

'get ParametersString from Dictionary
Public Shared Function getParametersString(ByVal items As Dictionary(Of String, String)) As String
Dim ls As New List(Of String)
For Each it In items
ls.Add(String.Format("{0}={1}", it.Key, it.Value))
Next
Return "?" & String.Join("&", ls)
End Function
...
Ivan каже…
...
'Send POST request
Public Shared Function sendPostRequest(ByVal url As String, ByVal parameters As String) As String
Dim httpWebRequest As HttpWebRequest = httpWebRequest.Create(url)
httpWebRequest.Method = "POST"
httpWebRequest.Accept = "*/*"
httpWebRequest.AllowAutoRedirect = True
httpWebRequest.ContentType = "application/x-www-form-urlencoded"
Dim data As Byte() = Encoding.UTF8.GetBytes(parameters)
httpWebRequest.ContentLength = data.Length
Dim newStream As Stream = httpWebRequest.GetRequestStream()
newStream.Write(data, 0, data.Length)
Dim raw_token As String = String.Empty
Try
Dim httpWebResponse As HttpWebResponse = httpWebRequest.GetResponse
Using sr As New StreamReader(httpWebResponse.GetResponseStream)
Using stream As IO.Stream = sr.BaseStream
'test response
'Dim str As String = New StreamReader(stream).ReadToEnd
'end test
Dim serializer As New DataContractJsonSerializer(GetType(OdnoklassnikiTokenResponse))
Dim dataStr As OdnoklassnikiTokenResponse = serializer.ReadObject(stream)
HttpContext.Current.Session("OdnoklassnikiOAuthProvider.Token") = dataStr
raw_token = dataStr.AccessToken

End Using
End Using
Catch ex As WebException
Dim exception As String = String.Empty
Using sr As New StreamReader(ex.Response.GetResponseStream)
exception = sr.ReadToEnd
End Using
Throw New WebException(exception)
Finally
newStream.Close()
End Try

Return raw_token
End Function


Class OdnoklassnikiTokenResponse
'access_token: 'kjdhfldjfhgldsjhfglkdjfg9ds8fg0sdf8gsd8fg',
'token_type: 'session',
'refresh_token: 'klsdjhf0e9dyfasduhfpasdfasdfjaspdkfjp'


Public Property AccessToken As String

Public Property TokenType As String

Public Property RefreshToken As String
End Class


'•uid - идентификатор пользователя
'•birthday - дата рождения
'•age - возраст пользователя
'•first_name - имя пользователя
'•last_name - фамилия пользователя
'•name - совмещение имени и фамилии для отображения
'•has_email - true/false имеет или не имеет адрес эл. почты
'•gender - пол
'•pic_1 - малый значок профиля (50x50)
'•pic_2 - малое изображение профиля (128x128)

Class OdnoklassnikiDataItem


Public Property UserId As String


Public Property Birthdate As String


Public Property Sex As String


Public Property Username As String


Public Property FirstName As String


Public Property LastName As String

End Class
End Class
End Namespace
Ivan каже…
Кстати насчет синтаксиса языков С# и VB - как для меня то выразительней VB - я обнаружил что когда читаешь код C# то тяжело быстро визуально отделить определения переменных от функций и процедур. В VB это более наглядно и четко разделено.
Unknown каже…
Круто.
Иван, спасибо! Попробую использовать.

Популярні дописи з цього блогу

Українська мова