Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

OWIN OAuth2 authentication via Social networks

4.85/5 (18 votes)
8 Feb 2015CPOL5 min read 50.7K   1.9K  
The Web API token based authentication via OWIN OAuth2 middleware supported by Facebook, Google, Microsoft.Extracting additional user's information from social networks.

Introduction

In this article you can find an example of the code, demo and explanation of how to authenticate users through social networks (Facebook, Google and Microsoft). How to extract an additional information about a user such as avatars, emails and full names.

You can view the demo and download the latest version of the code on my website: SupperSlonic.com

Image 1 Image 2 Image 3

Basic concepts

Owin Middleware

The Owin Middleware modules are responsible for handling the authentication with external authentication providers (such as Facebook, Google e.t.c.) and establishing an application session through a cookie. On all subsequent calls the application cookie middleware extracts the contents of the incoming application cookie and sets the claims identity of the current context.

What is a token?

A token is a string value that represents an encrypted list of System.Security.Claims. You can use any claims you like. My application has its own defined list of claims used across the project. Let's call them Application Claims.

Application Claims is a well-defined list of claims encrypted by OWIN into the issued token. Each time a service receives a token, it tries to decrypt it with its IIS machine key back into the list of claims and populate a User.Identity object.

To determine either it is an External Bearer token or a local one it checks the Issuer field of the claims. For a local one it must be always ClaimsIdentity.DefaultIssuer.
Note, that for the encryption OWIN uses the IIS machine key, that's why you must apply some custom solution for using the same token across several WEB-services.

Here is how I build my Application Claims list:

C#
private static ClaimsIdentity CreateIdentity(ClaimsMapper claimsMapper, string authenticationType)
{
	IList<claim> claims = new List<claim>();

	claims.Add(new Claim(ClaimTypes.NameIdentifier, claimsMapper.Id, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypes.Email, claimsMapper.Email, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypes.GivenName, claimsMapper.FullName, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypes.Sid, claimsMapper.Sid, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypes.Version, claimsMapper.Version, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypeIsVerified, claimsMapper.IsVerified, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
	claims.Add(new Claim(ClaimTypeAvatarUrl, claimsMapper.AvatarUrl, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));

	return new ClaimsIdentity(claims, authenticationType);
}
</claim></claim>

So, I can always get all this information from any token issued by my WEB-service. ClaimsMapper is an abstract strategy that knows how to map different data models to my claims list.

Authentication flows

OAuth2 authentication

How does an application get a user information from Facebook, Google e.t.c.?

All the information about a user received from an external provider (Facebook, Google e.t.c.) is encrypted in an external cookie.
Here is the description of the communication flow between an application and an external provider:

Image 4

C#
// GET api/Account/ExternalLogin
[AllowAnonymous]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)] //authenticated by external provider
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] //refresh token support
[Route("externalLogin", Name = "externalLogin")]
public async Task<ihttpactionresult> GetExternalLogin(string provider, string error = null)
{
	if (error != null)
	{
		return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
	}

	ExternalLoginProvider loginProvider;
	if (!Enum.TryParse<externalloginprovider>(provider, ignoreCase: true, result: out loginProvider) ||
		loginProvider == ExternalLoginProvider.None)
	{
		//Unsupported login provider
		return InternalServerError();
	}

	if (!User.Identity.IsAuthenticated)
	{
		return new ChallengeResult(loginProvider, this);
	}

	ExternalLoginModel externalLogin = ExternalLoginModel.FromIdentity(User.Identity as ClaimsIdentity);

	if (externalLogin == null)
	{
		return InternalServerError();
	}

	if (externalLogin.Provider != loginProvider)
	{
		Request.GetOwinContext().Authentication.SignOut(
			DefaultAuthenticationTypes.ExternalCookie,
			OAuthDefaults.AuthenticationType,
			CookieAuthenticationDefaults.AuthenticationType);
		return new ChallengeResult(loginProvider, this);
	}

	User user = await this.UserProvider.FindAsync(externalLogin.Provider, externalLogin.ProviderKey);
	if (user != null)
	{
		OwinHelper.SingIn(Request.GetOwinContext(), user, externalLogin);
	}
	else
	{
		OwinHelper.SingIn(Request.GetOwinContext(), externalLogin);
	}

	return Ok();
}
</externalloginprovider></ihttpactionresult>

Facebook user's data extraction:

C#
public class FacebookOAuthProvider : FacebookAuthenticationProvider
{
	private const string ApiBaseUrl = "https://graph.facebook.com";

	public override Task Authenticated(FacebookAuthenticatedContext context)
	{
		string avatarUrl = GetAvatarUrl(context.User.GetValue("id").ToString(), 240);
		context.Identity.AddClaim(
			new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));

		return base.Authenticated(context);
	}

	public static string GetAvatarUrl(string facebookUserId, int size)
	{
		return string.Format("{0}/{1}/picture?width={2}&height={2}",
			ApiBaseUrl,
			facebookUserId,
			size);
	}
}

Google user's data extraction:

C#
public class GoogleOAuthProvider : GoogleOAuth2AuthenticationProvider
{
	public override Task Authenticated(GoogleOAuth2AuthenticatedContext context)
	{
		string avatarUrl = context.User
			.SelectToken("image.url")
			.ToString()
			.Replace("sz=50", "sz=240");

		context.Identity.AddClaim(
			new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));

		return base.Authenticated(context);
	}
}

Microsoft user's data extraction:

C#
public class MicrosoftOAuthProvider : MicrosoftAccountAuthenticationProvider
{
	public override void ApplyRedirect(MicrosoftAccountApplyRedirectContext context)
	{
		context = new MicrosoftAccountApplyRedirectContext(
			context.OwinContext,
			context.Options,
			context.Properties,
			context.RedirectUri + "&display=touch"); //Mobile devices support

		base.ApplyRedirect(context);
	}

	public override Task Authenticated(MicrosoftAccountAuthenticatedContext context)
	{
		string avatarUrl = string.Format("https://apis.live.net/v5.0/{0}/picture",
						context.User.GetValue("id").ToString());

		context.Identity.AddClaim(
			new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));

		return base.Authenticated(context);
	}
}

Token issuance

Once a user is authenticated an application has three different flows for issuing tokens (detailed view on step 10):

  1. A new user is authorized through an external provider: Image 5 After an External Bearer token is issued, the user can register in the application using this token and then re-authorize again, to get a new token (Local Bearer).
    C#
    public class NotRegisteredExternal : ClaimsMapper
    {
    	public NotRegisteredExternal(ExternalLoginModel extLogin)
    	{
    		this.Id = string.Empty;
    		this.Email = extLogin.Email ?? string.Empty;
    		this.FullName = extLogin.FullName ?? string.Empty;
    		this.AvatarUrl = extLogin.AvatarUrl ?? string.Empty;
    		this.Sid = extLogin.ProviderKey;
    		this.Version = string.Empty;
    		this.IsVerified = false.ToString();
    		this.Issuer = extLogin.Provider.ToString();
    		this.OriginalIssuer = this.Issuer;
    	}
    }
  2. An existing user is authorized through an external provider: Image 6
    C#
    public class RegisteredExternal : ClaimsMapper
    {
    	public RegisteredExternal(User user, ExternalLoginModel extLogin)
    	{
    		this.Id = user.Id.ToString();
    		this.Email = user.Email;
    		this.FullName = user.FullName ?? string.Empty;
    		this.AvatarUrl = UserProvider.GetAvatarUrl(user);
    		this.Sid = extLogin.ProviderKey;
    		this.Version = this.GetVersion(user.TimeStamp);
    		this.IsVerified = user.IsVerified.ToString();
    		this.Issuer = ClaimsIdentity.DefaultIssuer;
    		this.OriginalIssuer = extLogin.Provider.ToString();
    	}
    }
  3. An existing user is authorized through a login/password: Image 7
    C#
    public class RegisteredLocal : ClaimsMapper
    {
    	public RegisteredLocal(User user)
    	{
    		this.Id = user.Id.ToString();
    		this.Email = user.Email;
    		this.FullName = user.FullName ?? string.Empty;
    		this.AvatarUrl = UserProvider.GetAvatarUrl(user);
    		this.Sid = string.Empty;
    		this.Version = this.GetVersion(user.TimeStamp);
    		this.IsVerified = user.IsVerified.ToString();
    		this.Issuer = ClaimsIdentity.DefaultIssuer;
    		this.OriginalIssuer = ClaimsIdentity.DefaultIssuer;
    	}
    }

Registering your application with external providers

Facebook Configuration

  1. Navigate to the Facebook Developers Page and log in by entering your Facebook credentials;
  2. If you aren’t already registered as a Facebook developer, click Register as a Developer and follow the directions to register;
  3. Under the My Apps tab, click + Add a New App button: Facebook new app
  4. Select a Website as an app platform: Facebook app type
  5. Enter an App Name and Category, then click Create App.
    This must be unique across Facebook. The App Namespace is the part of the URL that your App will use to access the Facebook application for authentication (for example, https://apps.facebook.com/{App Namespace}). If you don't specify an App Namespace, the App ID will be used for the URL. The App ID is a long system-generated number that you will see in the next step.
  6. On the Basic settings section of the page:
    • Enter Contact Email;
    • Enter Site URL that will send requests to Facebook.
    Facebook settings Note that only you will be able to authenticate using the email alias you have registered. Other users and test accounts will not be able to register.
    You can grant test users access to the application under the Roles menu.
    For all other Facebook accounts your application must be approved by Facebook. For futher instructions please view Status & Review menu.
  7. To disable sandbox mode for you app go to the Status & Review menu on the left and select Yes: Facebook settings

Google Configuration

  1. Navigate to the Google Developers Console;
  2. Click the Create Project button and enter a project name and ID (you can use the default values). In a few seconds the new project will be created and your browser will display the new projects page;
  3. In the left tab, click APIs & Auth, and then click Consent screen:
    • Enter email address;
    • Enter product name:
    Google Consent screen
  4. In the left tab, click APIs & Auth, and then click APIs:
    • Enable Google+ API to support user’s avatar access:
    Google+ API enabled
  5. In the left tab, click APIs & Auth and then click Credentials.
  6. Click the Create New Client ID under OAuth:
    • In the Create Client ID dialog, keep the default Web application for the application type;
    • Set the Authorized JavaScript origins to the SSL URL of the service, for example: https://supperslonic.com/;
    • Set the Authorized redirect URI to: https://supperslonic.com/signin-google.
    Google Client Id
  7. Copy and paste the AppId and App Secret into the Credentials.resx file for Google.

Microsoft Configuration

  1. Navigate to the Microsoft Developer Account;
  2. Press Create application reference;
  3. In Basic information enter valid Application name service URLs: Microsoft Basic Info
  4. In API Settings select that it is a mobile application and enter a valid redirect URL:
    • Note to add the signin-microsoft to your Redirect URLs.
    Microsoft API Settings
  5. In App Settings copy and paste the AppId and App Secret into the Credentials.resx file for Microsoft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)