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 ...
随机推荐
- GO_08:GO语言基础之interface
接口interface 1. 接口是一个或多个方法签名的集合 2. 只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口,这称为 Structural typing 3. ...
- python中的文件操作(2)
a+,w+,r+的特点: r+:r+模式允许读和写,当对文件句柄只进行写操作时,tell(),seek()为写操作的‘指针’(也就是写到seek()处). 当只进行读操作时,tell(),seek() ...
- MVVM模式原则
1.MVVM简介 这个模式的核心是ViewModel,它是一种特殊的model类型,用于表示程序的UI状态.它包含描述每个UI控件的状态的属性.例如,文本输入域的当前文本,或者一个特定按钮是否可用.它 ...
- 博世传感器调试笔记(一)----加速度传感器BMA253
公司是bosch的代理商,最近一段时间一直在公司开发的传感器demo板上调试bosch sensor器件.涉及到的器件有7,8款,类型包括重力加速度.地磁.陀螺仪.温度.湿度.大气压力传感器等.在调试 ...
- webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制
webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...
- CF&&CC百套计划3 Codeforces Round #204 (Div. 1) B. Jeff and Furik
http://codeforces.com/contest/351/problem/B 题意: 给出一个n的排列 第一个人任选两个相邻数交换位置 第二个人有一半的概率交换相邻的第一个数>第二个数 ...
- crontab定时任务_net
2017年2月25日, 星期六 crontab定时任务 19. crontab 定时任务 通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本.时间间隔 ...
- codeforces997C Sky full of stars
传送门:http://codeforces.com/problemset/problem/997/C [题解] 注意在把$i=0$或$j=0$分开考虑的时候,3上面的指数应该是$n(n-j)+j$ 至 ...
- bootstrap-table 应用
更多内容推荐微信公众号,欢迎关注: 前端代码:js初始化表格,使用服务器端分页:<!DOCTYPE html> <html> <head> <meta cha ...
- SpringMVC控制器 跳转到jsp页面 css img js等文件不起作用 不显示
今天在SpringMVC转发页面的时候发现跳转页面确实成功,但是JS,CSS等静态资源不起作用: 控制层代码: /** * 转发到查看培养方案详情的页面 * @return */ @RequestMa ...