我有一个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,它包含所有标准内容,例如:
等等。这些对我有帮助吗?
使用从here收集的一些代码,我提出了一个粗略的实现。
以下是发生的事情的简短摘要:
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
}
);
}