ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app
转载:http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-social-logins-facebook-google-angularjs-app/
Ok so it is time to enable ASP.NET Web API 2 external logins such as Facebook & Google then consume this in our AngularJS application. In this post we’ll add support to login using Facebook and Google+ external providers, then we’ll associate those authenticated social accounts with local accounts.
Once we complete the implementation in this post we’ll have an AngularJS application that uses OAuth bearer tokens to access any secured back-end API end point. This application will allow users to login using the resource owner password flow (Username/Password) along with refresh tokens, and support for using external login providers. So to follow along with this post I highly recommend to read previous parts:
- Token Based Authentication using ASP.NET Web API 2, Owin, and Identity – Part 1.
- AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity – Part 2.
- Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin – Part 3.
- Decouple OWIN Authorization Server from Resource Server – Part 5.
You can check the demo application, play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.
There is a great walkthrough by Rick Anderson which shows how you can enable end users to login using social providers, the walkthrough uses VS 2013 MVC 5 template with individual accounts, to be honest it is quite easy and straight forward to implement this if you want to use this template, but in my case and if you’r following along with previous parts, you will notice that I’ve built the Web API from scratch and I added the needed middle-wares manually.
The MVC 5 template with individual accounts is quite complex, there are middle-wares for cookies, external cookies, bearer tokens… so it needs time to digest how this template work and maybe you do not need all this features in your API. There are two good posts about describing how external logins implemented in this template by Brock Allen and Dominick Baier.
So what I tried to do at the beginning is to add the features and middle-wares needed to support external logins to my back-end API (I need to use the minimal possible code to implement this), everything went good but the final step where I should receive something called external bearer token is not happening, I ended up with the same scenario in this SO question. It will be great if someone will be able to fork my repo and try to find a solution for this issue without adding Nuget packages for ASP.NET MVC and end up with all the binaries used in VS 2013 MVC 5 template.
I didn’t want to stop there, so I decided to do custom implementation for external providers with little help from the MVC 5 template, this implementation follows different flow other than the one in MVC 5 template; so I’m open for suggestions, enhancements, best practices on what we can add to the implementation I’ll describe below.
Sequence of events which will take place during external login:
- AngularJS application sends HTTP GET request to anonymous end point (/ExternalLogin) defined in our back-end API by specifying client_id, redirect_uri, response_type, the GET request will look as this: http://ngauthenticationapi.azurewebsites.net/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=ngAuthApp& redirect_uri=http://ngauthenticationweb.azurewebsites.net/authcomplete.html (more on redircet_uri value later on post)
- Once the end point receives the GET request, it will check if the user is authenticated, and let we assume he is not authenticated, so it will notify the middleware responsible for the requested external provider to take the responsibility for this call, in our case it is Google.
- The consent screen for Google will be shown, and the user will provide his Google credentials to authenticate.
- Google will callback our back-end API and Google will set an external cookie containing the outcome of the authentication from Google (contains all the claims from the external provider for the user).
- Google middleware will be listing for an event named “Authenticated” where we’ll have the chance to read all external claims set by Google. In our case we’ll be interested in reading the claim named “AccessToken” which represents a Google Access Token, where the issuer for this claim is not LOCAL AUTHORITY, so we can’t use this access token directly to authorize calls to our secure back-end API endpoints.
- Then we’ll set the external provider external access token as custom claim named “ExternalAccessToken” and Google middleware will redirect back the end point (/ExternalLogin).
- Now the user is authenticated using the external cookie so we need to check that the client_id and redirect_uri set in the initial request are valid and this client is configured to redirect for the specified URI (more on this later).
- Now the code checks if the external user_id along with the provider is already registered as local database account (with no password), in both cases the code will issue 302 redirect to the specified URI in the redirect_uri parameter, this URI will contain the following (“External Access Token”, “Has Local Account”, “Provider”, “External User Name”) as URL hash fragment not a query string.
- Once the AngularJS application receives the response, it will decide based on it if the user has local database account or not, based on this it will issue a request to one of the end points (/RegisterExternal or /ObtainLocalAccessToken). Both end points accept the external access token which will be used for verification and then using it to obtain local access token issued by LOCAL AUTHORITY. This local access token could be used to access our back-end API secured end points (more on external token verification and the two end points later in post).
Sounds complicated, right? Hopefully I’ll be able to simplify describing this in the steps below, so lets jump to the implementation:
Step 1: Add new methods to repository class
The methods I’ll add now will add support for creating the user without password as well finding and linking external social user account with local database account, they are self explanatory methods and there is nothing special about them, so open file “AuthRepository” and paste the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public async Task<IdentityUser> FindAsync(UserLoginInfo loginInfo)
{
IdentityUser user = await _userManager.FindAsync(loginInfo);
return user;
}
public async Task<IdentityResult> CreateAsync(IdentityUser user)
{
var result = await _userManager.CreateAsync(user);
return result;
}
public async Task<IdentityResult> AddLoginAsync(string userId, UserLoginInfo login)
{
var result = await _userManager.AddLoginAsync(userId, login);
return result;
}
|
Step 2: Install the needed external social providers Nuget packages
In our case we need to add Google and Facebook external providers, so open Nuget package manger console and install the 2 packages below:
|
1
2
|
Install-Package Microsoft.Owin.Security.Facebook -Version 2.1.0
Install-Package Microsoft.Owin.Security.Google -Version 2.1.0
|
Step 3: Create Google and Facebook Application
I won’t go into details about this step, we need to create two applications one for Google and another for Facebook. I’ve followed exactly the steps used to create apps mentioned here so you can do this. We need to obtain AppId and Secret for both social providers and will use them later, below are the settings for my two apps:

Step 4: Add the challenge result
Now add new folder named “Results” then add new class named “ChallengeResult” which derives from “IHttpActionResult” then paste the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class ChallengeResult : IHttpActionResult
{
public string LoginProvider { get; set; }
public HttpRequestMessage Request { get; set; }
public ChallengeResult(string loginProvider, ApiController controller)
{
LoginProvider = loginProvider;
Request = controller.Request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
Request.GetOwinContext().Authentication.Challenge(LoginProvider);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
|
The code we’ve just implemented above will responsible to call the Challenge passing the name of the external provider we’re using (Google, Facebook, etc..), this challenge won’t fire unless our API sets the HTTP status code to 401 (Unauthorized), once it is done the external provider middleware will communicate with the external provider system and the external provider system will display an authentication page for the end user (UI page by Facebook or Google where you’ll enter username/password for authentication).
Step 5: Add Google and Facebook authentication providers
Now we want to add two new authentication providers classes where we’ll be overriding the “Authenticated” method so we’ve the chance to read the external claims set by the external provider, those set of external claims will contain information about the authenticated user and we’re interested in the claim named “AccessToken”.
As I’mentioned earlier this token is for the external provider and issued by Google or Facebook, after we’ve received it we need to assign it to custom claim named “ExternalAccessToken” so it will be available in the request context for later use.
So add two new classes named “GoogleAuthProvider” and “FacebookAuthProvider” to folder “Providers” and paste the code below to the corresponding class:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class GoogleAuthProvider : IGoogleOAuth2AuthenticationProvider
{
public void ApplyRedirect(GoogleOAuth2ApplyRedirectContext context)
{
context.Response.Redirect(context.RedirectUri);
}
public Task Authenticated(GoogleOAuth2AuthenticatedContext context)
{
context.Identity.AddClaim(new Claim("ExternalAccessToken", context.AccessToken));
return Task.FromResult<object>(null);
}
public Task ReturnEndpoint(GoogleOAuth2ReturnEndpointContext context)
{
return Task.FromResult<object>(null);
}
}
|
|
1
2
3
4
5
6
7
8
|
public class FacebookAuthProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
context.Identity.AddClaim(new Claim("ExternalAccessToken", context.AccessToken));
return Task.FromResult<object>(null);
}
}
|
Step 6: Add social providers middleware to Web API pipeline
Now we need to introduce some changes to “Startup” class, the changes as the below:
– Add three static properties as the code below:
|
1
2
3
4
5
6
7
8
|
public class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public static GoogleOAuth2AuthenticationOptions googleAuthOptions { get; private set; }
public static FacebookAuthenticationOptions facebookAuthOptions { get; private set; }
//More code here...
{
|
– Add support for using external cookies, this is important so external social providers will be able to set external claims, as well initialize the “OAuthBearerOptions” property as the code below:
|
1
2
3
4
5
6
7
8
|
public void ConfigureOAuth(IAppBuilder app)
{
//use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
//More code here.....
}
|
– Lastly we need to wire up Google and Facebook social providers middleware to our Owin server pipeline and set the AppId/Secret for each provider, the values are removed so replace them with your app values obtained from step 3:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//Configure Google External Login
googleAuthOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxx",
ClientSecret = "xxx",
Provider = new GoogleAuthProvider()
};
app.UseGoogleAuthentication(googleAuthOptions);
//Configure Facebook External Login
facebookAuthOptions = new FacebookAuthenticationOptions()
{
AppId = "xxx",
AppSecret = "xxx",
Provider = new FacebookAuthProvider()
};
app.UseFacebookAuthentication(facebookAuthOptions);
|
Step 7: Add “ExternalLogin” endpoint
As we stated earlier, this end point will accept the GET requests originated from our AngularJS app, so it will accept GET request on the form: http://ngauthenticationapi.azurewebsites.net/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=ngAuthApp& redirect_uri=http://ngauthenticationweb.azurewebsites.net/authcomplete.html
So Open class “AccountController” and paste the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
// GET api/Account/ExternalLogin
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
string redirectUri = string.Empty;
if (error != null)
{
return BadRequest(Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
var redirectUriValidationResult = ValidateClientAndRedirectUri(this.Request, ref redirectUri);
if (!string.IsNullOrWhiteSpace(redirectUriValidationResult))
{
return BadRequest(redirectUriValidationResult);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
IdentityUser user = await _repo.FindAsync(new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey));
bool hasRegistered = user != null;
redirectUri = string.Format("{0}#external_access_token={1}&provider={2}&haslocalaccount={3}&external_user_name={4}",
redirectUri,
externalLogin.ExternalAccessToken,
externalLogin.LoginProvider,
hasRegistered.ToString(),
externalLogin.UserName);
return Redirect(redirectUri);
}
|
By looking at the code above there are lot of things happening so let’s describe what this end point is doing:
a) By looking at this method attributes you will notice that its configured to ignore bearer tokens, and it can be accessed if there is external cookie set by external authority (Facebook or Google) or can be accessed anonymously. it worth mentioning here that this end point will be called twice during the authentication, first call will be anonymously and the second time will be once the external cookie is set by the external provider.
b) Now the code flow will check if the user has been authenticated (External cookie has been set), if it is not the case then Challenge Result defined in step 4 will be called again.
c) Time to validate that client and redirect URI which is set by AngularJS application is valid and this client is configured to allow redirect for this URI, so we do not want to end allowing redirection to any URI set by a caller application which will open security threat (Visit this post to know more about clients and Allowed Origin). To do so we need to add a private helper function named “ValidateClientAndRedirectUri”, you can add this to the “AccountController” class as the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
private string ValidateClientAndRedirectUri(HttpRequestMessage request, ref string redirectUriOutput)
{
Uri redirectUri;
var redirectUriString = GetQueryString(Request, "redirect_uri");
if (string.IsNullOrWhiteSpace(redirectUriString))
{
return "redirect_uri is required";
}
bool validUri = Uri.TryCreate(redirectUriString, UriKind.Absolute, out redirectUri);
if (!validUri)
{
return "redirect_uri is invalid";
}
var clientId = GetQueryString(Request, "client_id");
if (string.IsNullOrWhiteSpace(clientId))
{
return "client_Id is required";
}
var client = _repo.FindClient(clientId);
if (client == null)
{
return string.Format("Client_id '{0}' is not registered in the system.", clientId);
}
if (!string.Equals(client.AllowedOrigin, redirectUri.GetLeftPart(UriPartial.Authority), StringComparison.OrdinalIgnoreCase))
{
return string.Format("The given URL is not allowed by Client_id '{0}' configuration.", clientId);
}
redirectUriOutput = redirectUri.AbsoluteUri;
return string.Empty;
}
private string GetQueryString(HttpRequestMessage request, string key)
{
var queryStrings = request.GetQueryNameValuePairs();
if (queryStrings == null) return null;
var match = queryStrings.FirstOrDefault(keyValue => string.Compare(keyValue.Key, key, true) == 0);
if (string.IsNullOrEmpty(match.Value)) return null;
return match.Value;
}
|
d) After we validate the client and redirect URI we need to get the external login data along with the “ExternalAccessToken” which has been set by the external provider, so we need to add private class named “ExternalLoginData”, to do so add this class to the same “AccountController” class as the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public string ExternalAccessToken { get; set; }
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer) || String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name),
ExternalAccessToken = identity.FindFirstValue("ExternalAccessToken"),
};
}
}
|
e) Then we need to check if this social login (external user id with external provider) is already linked to local database account or this is first time to authenticate, based on this we are setting flag “hasRegistered” which will be returned to the AngularJS application.
f) Lastly we need to issue a 302 redirect to the “redirect_uri” set by the client application along with 4 values (“external_access_token”, “provider”, “hasLocalAccount”, “external_user_name”), those values will be added as URL hash fragment not as query string so they can be accessed by JS code only running on the return_uri page.
Now the AngularJS application has those values including external access token which can’t be used to access our secured back-end endpoints, to solve this issue we need to issue local access token with the help of this external access token. To do this we need to add two new end points where they will accept this external access token, validate it then generate local access token.
Why did we add two end points? Because if the external user is not linked to local database account; we need to issue HTTP POST request to the new endpoint “/RegisterExternal”, and if the external user already linked to local database account then we just need to obtain a local access token by issuing HTTP GET to endpoint “/ObtainLocalAccessToken”.
Before adding the two end points we need to add two helpers methods which they are used on these two endpoints.
Step 8: Verifying external access token
We’ve received the external access token from the external provider, and we returned it to the front-end AngularJS application, now we need to validate and make sure that this external access token which will be sent back from the front-end app to our back-end API is valid, and valid means (Correct access token, not expired, issued for the same client id configured in our back-end API). It is very important to check that external access token is issued to the same client id because you do not want our back-end API to end accepting valid external access tokens generated from different apps (client ids). You can read more about this here.
To do so we need to add class named “ExternalLoginModels” under folder “Models” and paste the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
namespace AngularJSAuthentication.API.Models
{
public class ExternalLoginViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string State { get; set; }
}
public class RegisterExternalBindingModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Provider { get; set; }
[Required]
public string ExternalAccessToken { get; set; }
}
public class ParsedExternalAccessToken
{
public string user_id { get; set; }
public string app_id { get; set; }
}
}
|
Then we need to add private helper function named “VerifyExternalAccessToken” to class “AccountController”, the implantation for this function as the below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
private async Task<ParsedExternalAccessToken> VerifyExternalAccessToken(string provider, string accessToken)
{
ParsedExternalAccessToken parsedToken = null;
var verifyTokenEndPoint = "";
if (provider == "Facebook")
{
//You can get it from here: https://developers.facebook.com/tools/accesstoken/
//More about debug_tokn here: http://stackoverflow.com/questions/16641083/how-does-one-get-the-app-access-token-for-debug-token-inspection-on-facebook
var appToken = "xxxxx";
verifyTokenEndPoint = string.Format("https://graph.facebook.com/debug_token?input_token={0}&access_token={1}", accessToken, appToken);
}
else if (provider == "Google")
{
verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
var client = new HttpClient();
var uri = new Uri(verifyTokenEndPoint);
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
dynamic jObj = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
parsedToken = new ParsedExternalAccessToken();
if (provider == "Facebook")
{
parsedToken.user_id = jObj["data"]["user_id"];
parsedToken.app_id = jObj["data"]["app_id"];
if (!string.Equals(Startup.facebookAuthOptions.AppId, parsedToken.app_id, StringComparison.OrdinalIgnoreCase))
{
return null;
}
}
else if (provider == "Google")
{
parsedToken.user_id = jObj["user_id"];
parsedToken.app_id = jObj["audience"];
if (!string.Equals(Startup.googleAuthOptions.ClientId, parsedToken.app_id, StringComparison.OrdinalIgnoreCase))
{
return null;
}
}
}
return parsedToken;
}
|
The code above is not the prettiest code I’ve written, it could be written in better way, but the essence of this helper method is to validate the external access token so for example if we take a look on Google case you will notice that we are issuing HTTP GET request to a defined endpoint by Google which is responsible to validate this access token, so if the token is valid we’ll read the app_id (client_id) and the user_id from the response, then we need to make sure that app_id returned as result from this request is exactly the same app_id used to configure Google app in our back-end API. If there is any differences then we’ll consider this external access token as invalid.
Note about Facebook: To validate Facebook external access token you need to obtain another single token for your application named appToken, you can get it from here.
Step 9: Generate local access token
Now we need to add another helper function which will be responsible to issue local access token which can be used to access our secure back-end API end points, the response for this token need to match the response we obtain when we call the end point “/token”, there is nothing special in this method, we are only setting the claims for the local identity then calling “OAuthBearerOptions.AccessTokenFormat.Protect” which will generate the local access token, so add new private function named “” to “AccountController” class as the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
|
Step 10: Add “RegisterExternal” endpoint to link social user with local account
After we added the helper methods needed it is time to add new API end point where it will be used to add the external user as local account and then link it with the created local account, this method should be accessed anonymously because we do not have local access token yet; so add new method named “RegisterExternal” to class “AccountController” as the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
// POST api/Account/RegisterExternal
[AllowAnonymous]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var verifiedAccessToken = await VerifyExternalAccessToken(model.Provider, model.ExternalAccessToken);
if (verifiedAccessToken == null)
{
return BadRequest("Invalid Provider or External Access Token");
}
IdentityUser user = await _repo.FindAsync(new UserLoginInfo(model.Provider, verifiedAccessToken.user_id));
bool hasRegistered = user != null;
if (hasRegistered)
{
return BadRequest("External user is already registered");
}
user = new IdentityUser() { UserName = model.UserName };
IdentityResult result = await _repo.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.UserName,
Login = new UserLoginInfo(model.Provider, verifiedAccessToken.user_id)
};
result = await _repo.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
//generate access token response
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);
return Ok(accessTokenResponse);
}
|
By looking at the code above you will notice that we need to issue HTTP POST request to the end point http://ngauthenticationapi.azurewebsites.net/api/account/RegisterExternal where the request body will contain JSON object of “userName”, “provider”, and “externalAccessToken” properties.
The code in this method is doing the following:
- once we receive the request we’ll call the helper method “VerifyExternalAccessToken” described earlier to ensure that this external access token is valid, and generated using our Facebook or Google application defined in our back-end API.
- We need to check if the user_id obtained from the external access token and the provider combination has already registered in our system, if this is not the case we’ll add new user using the username passed in request body without a password to the table “AspNetUsers”
- Then we need to link the local account created for this user to the provider and provider key (external user_id) and save this to table “AspNetUserLogins”
- Lastly we’ll call the helper method named “GenerateLocalAccessTokenResponse” described earlier which is responsible to generate the local access token and return this in the response body, so front-end application can use this access token to access our secured back-end API endpoints.
The request body will look as the image below:

Step 11: Add “ObtainLocalAccessToken” for linked external accounts
Now this endpoint will be used to generate local access tokens for external users who already registered with local account and linked their external identities to a local account, this method is accessed anonymously and will accept 2 query strings (Provider, ExternalAccessToken). The end point can be accessed by issuing HTTP GET request to URI: http://ngauthenticationapi.azurewebsites.net/api/account/ObtainLocalAccessToken?provider=Facebook&externalaccesstoken=CAAKEF……….
So add new method named “ObtainLocalAccessToken” to controller “AccountController” and paste the code below:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
[AllowAnonymous]
[HttpGet]
[Route("ObtainLocalAccessToken")]
public async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken)
{
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(externalAccessToken))
{
return BadRequest("Provider or external access token is not sent");
}
var verifiedAccessToken = await VerifyExternalAccessToken(provider, externalAccessToken);
if (verifiedAccessToken == null)
{
return BadRequest("Invalid Provider or External Access Token");
}
IdentityUser user = await _repo.FindAsync(new UserLoginInfo(provider, verifiedAccessToken.user_id));
bool hasRegistered = user != null;
if (!hasRegistered)
{
return BadRequest("External user is not registered");
}
//generate access token response
var accessTokenResponse = GenerateLocalAccessTokenResponse(user.UserName);
return Ok(accessTokenResponse);
}
|
By looking at the code above you will notice that we’re doing the following:
- Make sure that Provider and ExternalAccessToken query strings are sent with the HTTP GET request.
- Verifying the external access token as we did in the previous step.
- Check if the user_id and provider combination is already linked in our system.
- Generate a local access token and return this in the response body so front-end application can use this access token to access our secured back-end API endpoints.
Step 12:Updating the front-end AngularJS application
I’ve updated thelive front-end application to support using social logins as the image below:

Now once the user clicks on one of the social login buttons, a popup window will open targeting the URI: http://ngauthenticationapi.azurewebsites.net/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=ngAuthApp &redirect_uri=http://ngauthenticationweb.azurewebsites.net/authcomplete.html and the steps described earlier in this post will take place.
What worth mentioning here that the “authcomplete.html” page is an empty page which has JS function responsible to read the URL hash fragments and pass them to AngularJS controller in callback function, based on the value of the fragment (hasLocalAccount) the AngularJS controller will decide to call end point “/ObtainLocalAccessToken” or display a view named “associate” where it will give the end user the chance to set preferred username only and then call the endpoint “/RegisterExternal”.
The “associate” view will look as the image below:

That is it for now folks! Hopefully this implementation will be useful to integrate social logins with your ASP.NET Web API and keep the API isolated from any front end application, I believe by using this way we can integrate native iOS or Android Apps that using Facebook SDKs easily with our back-end API, usually the Facebook SDK will return Facebook access token and we can then obtain local access token as we described earlier.
If you have any feedback, comment, or better way to implement this; then please drop me comment, thanks for reading!
You can check the demo application, play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.
Follow me on Twitter @tjoudeh
References
- Informative post by Brock Allen about External logins with OWIN middleware.
- Solid post by Dominick Baier on Web API Individual Accounts.
- Great post by Rick Anderson on VS 2013 MVC 5 template with individual accounts.
- Helpful post by Valerio Gheri on How to login with Facebook access token.
ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app的更多相关文章
- Professional C# 6 and .NET Core 1.0 - Chapter 42 ASP.NET Web API
本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处: -------------------------------------------------------- ...
- Web API 2 入门——使用ASP.NET Web API和Angular.js构建单页应用程序(SPA)(谷歌翻译)
在这篇文章中 概观 演习 概要 由网络营 下载网络营训练包 在传统的Web应用程序中,客户机(浏览器)通过请求页面启动与服务器的通信.然后,服务器处理请求,并将页面的HTML发送给客户端.在与页面的后 ...
- Getting Started with ASP.NET Web API 2 (C#)
By Mike Wasson|last updated May 28, 2015 7556 of 8454 people found this helpful Print Download Com ...
- ASP.NET Web API与Rest web api(一)
HTTP is not just for serving up web pages. It is also a powerful platform for building APIs that exp ...
- ASP.NET Web API与Rest web api(一)
本文档内容大部分来源于:http://www.cnblogs.com/madyina/p/3381256.html HTTP is not just for serving up web pages. ...
- 【ASP.NET Web API教程】1.1 第一个ASP.NET Web API
Your First ASP.NET Web API (C#)第一个ASP.NET Web API(C#) By Mike Wasson|January 21, 2012作者:Mike Wasson ...
- [转]Getting Started with ASP.NET Web API 2 (C#)
http://www.asp.net/web-api 本文转自:http://www.asp.net/web-api/overview/getting-started-with-aspnet-web- ...
- 适用于app.config与web.config的ConfigUtil读写工具类 基于MongoDb官方C#驱动封装MongoDbCsharpHelper类(CRUD类) 基于ASP.NET WEB API实现分布式数据访问中间层(提供对数据库的CRUD) C# 实现AOP 的几种常见方式
适用于app.config与web.config的ConfigUtil读写工具类 之前文章:<两种读写配置文件的方案(app.config与web.config通用)>,现在重新整理一 ...
- 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用
由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...
随机推荐
- Ansible6:Playbook简单使用
目录 一个简单的示例 通过Playbook安装apache示例 playbook的构成 Hosts和Users 任务列表和action handlers tags 示例 ansbile-playboo ...
- 进化论VS中性突变理论
进化论VS中性突变理论 查尔斯·罗伯特·达尔文(英语:CharlesRobert Darwin,1809年2月12日-1882年4月19日),英国生物学家,其“进化论”被列为19世纪自然科学的三大发现 ...
- 我的编码习惯 - Controller规范
原文出处: 晓风轻 请先阅读我这2篇文章 程序员你为什么这么累? 和 我的编码习惯 - 接口定义. 第一篇文章中,我贴了2段代码,第一个是原生态的,第2段是我指定了接口定义规范,使用AOP技术之后最终 ...
- SQL Server 2008定期的备份数据库--差异+完整
https://www.cnblogs.com/l1pe1/p/7885207.html https://www.cnblogs.com/tylerflyn/p/8051398.html https: ...
- 转:NSString / NSData / char* 类型之间的转换
1. NSString转化为UNICODE String: (NSString*)fname = @“Test”; char fnameStr[10]; memcpy(fnameStr, [fname ...
- 15 Most Read Data Science Articles in 2015. So far …
15 Most Read Data Science Articles in 2015. So far … We've compiled the latest set of "most rea ...
- 同一条sql语句,只是改变了搜索的条件,就很慢?
重建索引: ) 显示索引信息: dbcc showcontig('表名’) 具体参考:http://www.cnblogs.com/bluedy1229/p/3227167.html
- pf
here Pro 排列n个不同的数成为长度为p的序列 每两个相同的数之间至少要隔着m个数 求排列总方案数 Input 三个整数 n,m,p output 输出一个数字表示序列组成方法,由于结果可能很大 ...
- 浅析XSS与XSSI异同
浅析XSS与XSSI异同 这篇文章主要介绍了XSS与XSSI异同,跨站脚本(XSS)和跨站脚本包含(XSSI)之间的区别是什么?防御方法有什么不同?感兴趣的小伙伴们可以参考一下 Michael Cob ...
- RPC简介与hdfs读过程与写过程简介
1.RPC简介 Remote Procedure Call 远程过程调用协议 RPC——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些 ...