Реализация авторизации с помощью ВКонтакте используя Microsoft.Owin.Security

.NET Вконтакте Owin Katana C#

При создании нового Web проекта в Visual Studio 2013 нам предлагается готовый шаблон сайта с уже реализованной системой авторизации с помощью различных популярных в мире сервисов авторизации. Среди них есть Microsoft Account, Facebook, Twitter и Google. Это чудесное API называется Katana project.

Однако поддержки ВКонтакте "из коробки" нет. Оно и понятно - API писали люди, которые скорее всего ничего про ВКонтакте не знают.

Как бы многие IT-шники не плевались, а на территории СНГ "вконтактик" очень популярен и отказывать сабе в интеграции с ним только потому, что он кому-то не нравится - глупо. Однако, я повторюсь, "из коробки" его нет.

Ну, нет - так нет. Напишем сами.

Начнем мы с того, с чего началось создание самого ВКонтакта. А именно с копирования Facebook'а. В нашем случае мы возьмем реализацию для Facebook'а и адаптируем ее для ВКонтакта.

Забираем исходники Катаны:

git clone https://git01.codeplex.com/katanaproject

И смотрим в папку Microsoft.Owin.Security.Facebook.

Бессовестно копируем ее, попутно заменяя все слова Facebook в именах классов на Vk. Понятно, что одним переименованием классов дело не закончится. Начинаем менять код.

В классе Constants меняем поле DefaultAuthenticationType

public const string DefaultAuthenticationType = "ВКонтакте";

Слегка правим класс VkAuthenticatedContext

public class VkAuthenticatedContext : BaseContext
{
    public VkAuthenticatedContext(IOwinContext context, JObject user, string accessToken): base(context)
    {
        User = user;
        AccessToken = accessToken;

        Id = TryGetValue(user, "uid");
        UserName = (TryGetValue(user, "first_name") + " " + TryGetValue(user, "last_name")).Trim();
    }

    public JObject User { get; private set; }
    public string AccessToken { get; private set; }

    public string Id { get; private set; }
    public string UserName { get; private set; }

    public ClaimsIdentity Identity { get; set; }
    public AuthenticationProperties Properties { get; set; }

    private static string TryGetValue(JObject user, string propertyName)
    {
        JToken value;
        return user.TryGetValue(propertyName, out value) ? value.ToString() : <strong>string.Empty</strong>;
    }
}

Идем дальше. Класс VkAuthenticationOptions:

class VkAuthenticationOptions {
    // ...
    public VkAuthenticationOptions(): base(Constants.DefaultAuthenticationType)
    {
        Caption = Constants.DefaultAuthenticationType;
        ReturnEndpointPath = "/signin-vk";
        AuthenticationMode = AuthenticationMode.Passive;
        Scope = new List<string>();
        BackchannelTimeout = TimeSpan.FromSeconds(60);
    }
    // ...        
}

Меняем точку возврата после авторизации. На этот URL будет отправлен код, который нужен для получения access_token'а.

Теперь самое интересное - класс VkAuthenticationHandler

В методе:

protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()

Меняем следующий фрагмент:

string tokenEndpoint = "https://oauth.vk.com/access_token";

string requestPrefix = Request.Scheme + "://" + Request.Host;
string redirectUri = requestPrefix + Request.PathBase + Options.ReturnEndpointPath;

string tokenRequest = 
    "?client_id=" + Uri.EscapeDataString(Options.AppId) +
    "&client_secret=" + Uri.EscapeDataString(Options.AppSecret) +
    "&code=" + Uri.EscapeDataString(code) +
    "&redirect_uri=" + Uri.EscapeDataString(redirectUri);

HttpResponseMessage tokenResponse = await _httpClient.GetAsync(tokenEndpoint + tokenRequest, Request.CallCancelled);
tokenResponse.EnsureSuccessStatusCode();
string text = await tokenResponse.Content.ReadAsStringAsync();
var form = JsonConvert.DeserializeObject<Dictionary<string, object>>(text);

var accessToken = (string)form["access_token"];
var userId = (long)form["user_id"];

string graphApiEndpoint = "https://api.vk.com/method/users.get" +
    "?user_id=" + userId +
    "&fields=" +
    "&name_case=Nom" +
    "&access_token=" + Uri.EscapeDataString(accessToken);

HttpResponseMessage graphResponse = await httpClient.GetAsync(graphApiEndpoint, Request.CallCancelled);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
JObject data = JObject.Parse(text);
var user = (JObject) data["response"].First;

var context = new VkAuthenticatedContext(Context, user, accessToken);

а в методе:

protected override Task ApplyResponseChallengeAsync()`

вот такой фрагмент:

string authorizationEndpoint =
    "https://oauth.vk.com/authorize" +
    "?client_id=" + Uri.EscapeDataString(Options.AppId ?? string.Empty) +
    "&scope=" + Uri.EscapeDataString(scope) +
    "&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
    "&response_type=code" +
    "&state=" + Uri.EscapeDataString(state);

И почти финал. В классе VkAuthenticationExtensions меняем имя метода UseFacebookAuthentication на UseVkAuthentication.

Теперь финал. Собираем и подключаем к проекту:

public void Configuration(IAppBuilder app)
{
    // ...
    app.UseVkAuthentication(
        appId: "999999",
        appSecret: "z5ZkSw1bkQV88YMRYA7vg");
    // ...
}

И наступает счастье.

Все и сразу мужно скачать вот здесь.