Enabling Social logins with ASP.NET Web API 2.0 and Ionic Cordova app

守給你的承諾、 提交于 2019-12-01 15:26:26

Since you already have the access token from your prefered social auth, you can pass that to ASP.NET. However, it does not have a way to process that, which you can add by following this answer also elaborated at the blog here.

Which will return an auth token you can use with auth header

P.S. Don't know if I should copy all code here as well? It's too big.

Using some code gleaned from here, I've come up with a rough implementation.

Here is a short summary of what is happening:

  1. I use the Cordova GooglePlus plugin to log the user in at the client-side. This will supply us with an OAuth access token.
  2. I have a new method on my AccountController which I have called 'RegisterExternalToken'. I make a call to this function from my mobile application, and I supply the access token.
  3. The 'RegisterExternalToken' method will validate the access token by calling the following endpoint: https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123
  4. The tokeninfo endpoint returns a HTTP 200 response containing the details of the user profile. I check this then add an identity claim.
  5. I check with the ASP.Net Identity UserManager to see if the user is registered already. If not, I register and create a new account. Otherwise, I just sign the user in.
  6. Much like the existing ASP.NET Identity 'GrantResourceOwnerCredentials' method on the /Token endpoint, I then generate a new access token and return it in a JSON response object which mirrors the object that gets returned via the ASP.NET /token endpoint.
  7. At the client-side, I parse the JSON to retrieve the access token the same way I do for a normal non-external login and supply this access token as the bearer token in the header of all subsequent authenticated requests. I also needed to decorate each of my API controllers with the following attributes:

    [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] [HostAuthentication(DefaultAuthenticationTypes.ApplicationCookie)]

AccountController.cs

    // POST /api/Account/RegisterExternalToken
    [OverrideAuthentication]
    [AllowAnonymous]
    [Route("RegisterExternalToken")]
    public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var externalLogin = await ExternalLoginData.FromToken(model.Provider, model.Token);

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

            if (externalLogin.LoginProvider != model.Provider)
            {
                Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
                return InternalServerError();
            }

            var user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
                externalLogin.ProviderKey));

            var hasRegistered = user != null;
            ClaimsIdentity identity;
            if (hasRegistered)
            {
                identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
                var claims = externalLogin.GetClaims();
                identity.AddClaims(claims);
                Authentication.SignIn(identity);
            }
            else
            {
                user = new ApplicationUser
                {
                    Id = Guid.NewGuid().ToString(),
                    UserName = model.Email,
                    Email = model.Email
                };

                var result = await UserManager.CreateAsync(user);
                if (!result.Succeeded)
                {
                    return GetErrorResult(result);
                }

                // Specific to my own app, I am generating a new customer account for a newly registered user
                await CreateCustomer(user);

                var info = new ExternalLoginInfo
                {
                    DefaultUserName = model.Email,
                    Login = new UserLoginInfo(model.Provider, externalLogin.ProviderKey)
                };

                result = await UserManager.AddLoginAsync(user.Id, info.Login);

                if (!result.Succeeded)
                {
                    return GetErrorResult(result);
                }

                identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
                var claims = externalLogin.GetClaims();
                identity.AddClaims(claims);
                Authentication.SignIn(identity);
            }

            var authenticationProperties = ApplicationOAuthProvider.CreateProperties(model.Email);
            var authenticationTicket = new AuthenticationTicket(identity, authenticationProperties);
            var currentUtc = new SystemClock().UtcNow;
            authenticationTicket.Properties.IssuedUtc = currentUtc;
            authenticationTicket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
            var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(authenticationTicket);
            Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            // Generate JSON response object
            var token = new JObject(
                new JProperty("userName", user.UserName),
                new JProperty("id", user.Id),
                new JProperty("access_token", accessToken),
                new JProperty("token_type", "bearer"),
                new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
                new JProperty(".issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
                new JProperty(".expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
            );


            return Ok(token);
        }
        catch (Exception e)
        {
            return BadRequest("Unable to login due to unspecified error.");
        }

ExternalLoginData.cs - (I moved the original version of this from the AccountController.cs to it's own separate file)

public class ExternalLoginData
{
    public string LoginProvider { get; set; }
    public string ProviderKey { get; set; }
    public string UserName { get; set; }

    public IList<Claim> GetClaims()
    {
        IList<Claim> claims = new List<Claim>();
        claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));

        if (UserName != null)
        {
            claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
        }

        return claims;
    }

    public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
    {
        var providerKeyClaim = identity?.FindFirst(ClaimTypes.NameIdentifier);

        if (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)
        };
    }

    public static async Task<ExternalLoginData> FromToken(string provider, string accessToken)
    {
        string verifyTokenEndPoint = "";
        string verifyAppEndPoint = "";

        if (provider == "Google")
        {
            verifyTokenEndPoint = $"https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={accessToken}";
        }
        else
        {
            return null;
        }

        var client = new HttpClient();
        var uri = new Uri(verifyTokenEndPoint);
        var response = await client.GetAsync(uri);
        ClaimsIdentity identity = null;
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            dynamic verifyAppJsonObject = (JObject) JsonConvert.DeserializeObject(content);

            identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
            if (provider == "Google")
            {
                // TODO: Verify contents of verifyAppJsonObject
                identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, Startup.GoogleClientId, ClaimValueTypes.String, "Google", "Google"));
            }
        }

        var providerKeyClaim = identity?.FindFirst(ClaimTypes.NameIdentifier);

        if (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)
        };
    }
}

In the above code, Startup.GoogleClientId is just the string value of the Google Client ID used here:

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
    ClientId = GoogleClientId,
    ClientSecret = "####"
});

Client side in my Ionic app I am calling the method like so:

loginWithGoogle(socialLogin : RegisterExternalTokenBindingModel){

    return new Promise((resolve, reject) => {
        this.http.post(
            `${this.baseUrl}/api/Account/RegisterExternalToken`,
            socialLogin,
            new RequestOptions()
        ).subscribe(
            result => {
                resolve(result.json());
            },
            error => {
                console.log("Login error: "+  error.text());
            }
        )
    })
}

Here I just parse the access token and set the value in my UserAccountService class and save it to localStorage as well:

 loginWithGoogle(socialLogin : RegisterExternalTokenBindingModel){
    return this.apiService.loginWithGoogle(socialLogin)
      .then(
        success => {
          let accessToken = JsonPath.query(success, 'access_token');
          this.accessToken = accessToken;
          this.storage.set(this.storageAccessToken, this.accessToken);
          return new LoginResult(true, accessToken);
        },
        failure => {
            // TODO: Error handling
        }
      );
  }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!