Implementing VKontakte sign-in with Microsoft.Owin.Security

When you create a new Web project in Visual Studio 2013, you get a ready-made site template with sign-in already implemented through various authentication services popular around the world. Among them are Microsoft Account, Facebook, Twitter and Google. This wonderful API is called the Katana project.

However, there is no VKontakte support "out of the box". That's understandable: the API was written by people who most likely know nothing about VKontakte.

However much many IT folks may grumble about it, "VK" is very popular across the CIS, and refusing to integrate with it just because somebody doesn't like it would be silly. But, I'll say it again, it's not there "out of the box".

Well, if it's not there, it's not there. We'll write it ourselves.

We'll start where VKontakte itself started: by copying Facebook. In our case, we'll take the Facebook implementation and adapt it for VKontakte.

Grab the Katana sources:

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

And look at the folder Microsoft.Owin.Security.Facebook.

Shamelessly copy it, replacing every occurrence of Facebook in class names with Vk along the way. Obviously, renaming the classes won't be enough. Let's start changing the code.

In the Constants class, change the DefaultAuthenticationType field

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

Slightly adjust the VkAuthenticatedContext class

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>;
	}
}

Moving on. The VkAuthenticationOptions class:

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

We change the return endpoint used after sign-in. The code needed to obtain the access_token will be sent to this URL.

Now the most interesting part: the VkAuthenticationHandler class

In the method:

protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()

Change the following fragment:

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);

and in the method:

protected override Task ApplyResponseChallengeAsync()`

this fragment:

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);

Almost done. In the VkAuthenticationExtensions class, rename the UseFacebookAuthentication method to UseVkAuthentication.

And now the finale. Build it and plug it into the project:

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

And happiness ensues.

You can download everything in one go here.

Only registered users can leave a comment.

Sign in or register to leave a comment.