使用ASP.NET Web API 2.0和Ionic Cordova应用程序启用社交登录

问题描述 投票:11回答:2

我有一个ASP.NET Web API 2.0应用程序,我已连接到一个Ionic应用程序,它使用我的API进行登录,注册等。

我正在使用基于令牌的身份验证,以便当用户注册帐户并登录时,他们将被授予访问令牌,该令牌在每个后续请求的标头中传递并用于对用户进行身份验证。这很好。

现在,我希望允许用户通过登录Facebook或Google等社交帐户来注册帐户。

目前,我正在尝试集成Google身份验证,因此在我的Startup.Auth.cs文件中,我启用了它,如下所示:

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

我也有标准的AccountController方法,所以从我的Ionic应用程序,我能够向'RegisterExternal'方法发出GET请求,如下所示:

/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=self&redirect_uri=###

据我了解,此方法返回一个重定向URI,我应该导航到我的应用程序中以允许用户登录。我想我会在我的应用程序中打开一个新窗口以允许用户输入他们的详细信息?

但是,我不认为这是我想采取的方法。对于大多数应用程序,这些天我只需按下“使用Google登录”按钮,它可以完成所有魔术,无需重定向或输入任何信息。

我正在看看Cordova GooglePlus plugin,这似乎是我需要的,因为它允许用户登录客户端。成功回调还会返回以下内容:

 obj.email          // '[email protected]'
 obj.userId         // user id
 obj.displayName    // 'Eddy Verbruggen'
 obj.familyName     // 'Verbruggen'
 obj.givenName      // 'Eddy'
 obj.imageUrl       // 'http://link-to-my-profilepic.google.com'
 obj.idToken        // idToken that can be exchanged to verify user identity.
 obj.serverAuthCode // Auth code that can be exchanged for an access token and refresh token for offline access
 obj.accessToken    // OAuth2 access token

所以我的问题是,我是否可以使用此信息传递给我的ASP.NET服务的帐户服务来验证用户并为他们创建一个帐户(如果他们还没有)?

我读了here,如果你使用谷歌登录与后端服务器通信的应用程序,你可以通过将用户的ID令牌发送到我的服务器来验证它并创建一个帐户,从而识别服务器上当前登录的用户用户尚未在我的数据库中。

这表明我应该能够使用此插件将我需要的信息发送到我的服务器。如果可以,我需要触及哪个端点,我需要做什么?

我有一个AccountController.cs,它包含所有标准内容,例如:

  • AddExternalLogin
  • GetExternalLogin
  • RegisterExternal

等等。这些对我有帮助吗?

asp.net cordova asp.net-web-api ionic-framework google-authentication
2个回答
1
投票

由于您已经拥有来自首选社交身份验证的访问令牌,因此您可以将其传递给ASP.NET。但是,它没有办法处理它,你可以通过跟随这个answer在博客here上详细阐述。

这将返回一个可以与auth header一起使用的身份验证令牌

附:不知道我是否应该在这里复制所有代码?这个太大了。


0
投票

使用从here收集的一些代码,我提出了一个粗略的实现。

以下是发生的事情的简短摘要:

  1. 我使用Cordova GooglePlus插件在客户端登录用户。这将为我们提供OAuth访问令牌。
  2. 我的AccountController上有一个新方法,我称之为'RegisterExternalToken'。我从我的移动应用程序调用此函数,并提供访问令牌。
  3. 'RegisterExternalToken'方法将通过调用以下端点来验证访问令牌:https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123
  4. tokeninfo端点返回包含用户配置文件详细信息的HTTP 200响应。我检查一下然后添加身份声明。
  5. 我检查ASP.Net Identity UserManager以查看用户是否已经注册。如果没有,我会注册并创建一个新帐户。否则,我只是在用户签名。
  6. 与/ Token端点上现有的ASP.NET Identity'GrantResourceOwnerCredentials'方法非常相似,然后我生成一个新的访问令牌并将其返回到JSON响应对象中,该对象镜像通过ASP.NET /令牌端点返回的对象。
  7. 在客户端,我解析JSON以检索访问令牌的方式与我正常的非外部登录方式相同,并在所有后续经过身份验证的请求的标头中提供此访问令牌作为承载令牌。我还需要使用以下属性装饰每个API控制器: [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 - (我将其原始版本从AccountController.cs移到了它自己的独立文件中)

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

在上面的代码中,Startup.GoogleClientId只是此处使用的Google客户端ID的字符串值:

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

我的Ionic应用程序中的客户端我正在调用这样的方法:

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

在这里,我只解析访问令牌并在UserAccountService类中设置值,并将其保存到localStorage:

 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
        }
      );
  }
© www.soinside.com 2019 - 2024. All rights reserved.