ASP.NET Web API and External Login - Authenticating with Social Networks
Table of Contents
The ASP.NET Web API project created from the default template in Visual Studio 2013 comes with an option to choose the Authentication method. The 'Individual User Accounts' option of authorization will allow users of your API to authenticate using exisitng their exisitng social networks(Facebook, Twitter, Google or Microsoft). This is a very useful feature and also speeds up the signing process, as there is little or no input required from the user and also one less password less to remember. All these social logins uses OAuth to provide authorized access to their services, and there are exisitng Owin adapters for these social networks. You could very well write a custom owin adapter to plug new authorization providers.
In this post we will see how we can enable Facebook login for your API when the client and api are hosted on seperate domains.
We will start off with the default template for the Web API, created by Visual Studio 2013 for Web API(File->New Project->ASP.NET Web Application->Web API with Authentication set to Individual User Accounts)
The Account Controller has all the methods that is required for a client to authenticate with the api and get the token to access protected resources. We will go through the calls that the client app would need to perform a successful authentication. I have updated the owin nuget packages to use the latest Owin 3.0.0 packages.
1. Getting the supported Authentication providers
The client in it login screen would be showing the allowed authorization providers along with the normal username/password login option. The ExternalLogins endpoint returns all the supported external authentication providers. The supported list of providers can be added in Startup.Auth.cs
app.UseFacebookAuthentication(appId: "APPID", appSecret: "APPSECRET");
The application id and secret can be obtained by registering for an application with the corresponding provider. For facebook you can do that here. You would also need to configure the web api endpoint as a website platform when obtaining the api keys. Now on querying the endoint the client will get back the endpoint that it needs to hit when an authentication with Facebook is required. A redirect url is to be passsed along with the request, to which the api will redirect to after succesfull authentication with the token. By default email is not received from facebook, but you can override this behaviour by providing some extra options when registering the provider as shown below. The OnAuthenticated method on the FacebookProvider gets called once the user is authenticated with facebook. At this point you can get the Email details explicitly and add it to the claims.
var facebookProvider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
// Add the email id to the claim
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email));
return Task.FromResult(0);
}
};
var options = new FacebookAuthenticationOptions()
{
AppId = "AppId",
AppSecret = "AppSecret",
Provider = facebookProvider
};
options.Scope.Add("email");
app.UseFacebookAuthentication(options);
You can also get the facebook token if required from the context and which can be used in the database to update information to facebook on behalf of the user.
2. Making a request to an Authentication provider
Now that we have got the list of supported list of providers, we can make the request to the required provider from the client. This is simply done by navigating to the url obtained for the corresponding provider in the previous request, i.e to the ExternalLogin endpoint. If the user is already authenticated (detected via cookie), then the user is redirected to the redirect url provided in above step. If the user is not authenticated then the login page of the corresponding provider will be shown and after a successfull login the redirection will happen. The token that is received here is the custom token that gets generated by the web-api and should not be mistaken for the authorization server's(facebook) token itself.
The owin middleware abstracts away the interaction with the authorization server, Facebook in our case here. On successfull login in facebook, it redirects to '/signin-facebook along with the facebook token. We can get the username or email from the claims provided by the authentication server that can then be passed back to the client for registering the user in the next step. The ExternalLoginData class get the data from the claims. I have modified this to use the new Email property that we have added. To return this email address to the client, we need to override the method as shown below in ApplicationOAuthProvider
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
// Add the claims to the return url
foreach (var claim in context.Identity.Claims)
{
if (claim.Type == ClaimsIdentity.DefaultNameClaimType)
{
context.AdditionalResponseParameters.Add("username", claim.Value);
}
else if (claim.Type == ClaimTypes.Email)
{
context.AdditionalResponseParameters.Add("email", claim.Value);
}
}
return base.AuthorizationEndpointResponse(context);
}
The above method adds in the additional response parameters that gets embedded in the redirect url to the client.
3. Register user
Now that the user is successfully authenticated with the authorization provider and we have a token, we need to register the user into our database, as we dont want to go to facebook to verify the identity for each and every request. The client can call into RegisterExternal method to register the user into our system. It expects us to pass a email/username for the external authenticated user, which we would have already got from the redirect url parameters. Now the external authenticated user is a valid user in our system and is allowed to make authenticated calls to the api.For the call to RegisterExternal we need to add the token that we got as part of the redirecturl as part of the Authorization header.
var registerUser = function () {
$.ajax({
url: config.apiBaseUrl + 'api/Account/RegisterExternal',
data: { 'Email': email, 'Name' : email},
method: 'POST',
xhrFields: {
withCredentials: true
},
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization','Bearer ' + token);
},
success: function(data) {
// Navigate to the user page
window.location = "#user";
},
failure: function(data) {
alert('Registration failed' + data.toString());
}
});
On successfull registeration we can get the User details by calling to the UserInfo endpoint.
var userInfoUrl = config.apiBaseUrl + 'api/Account/UserInfo';
$.ajax({
url: userInfoUrl,
success: function (data) {
window.location = '#';
},
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + config.token);
},
});
We can mofify the models to handle more information as required by our api. We could save the token in a local storage and retrieve it whenever a user visits the site back again. The same is the approach for integrating with the other social networks that we have. The sample using the facebook provider is availabe here.
Rahul Nath Newsletter
Join the newsletter to receive the latest updates in your inbox.