我正在创建一个移动应用程序(Xamarin + MvvmCross),它访问我的服务Web API 2 + Owin并试图支持外部登录。对于以下内容,假设我已经使用外部身份验证注册了用户,并且只想从移动应用程序登录用户。
目前,我的 Facebook 登录主要在 AngularJS 应用程序中使用 Facebook 和 Google 的 ASP.NET Web API 2 外部登录之后工作,但是经过几个小时后,我无法弄清楚如何为 Microsoft 登录实施相同的策略,因为我不能找到一种方法来验证 Microsoft 访问令牌。
我的理解是,为了调用我自己的 API(例如,获取我的用户数据和对象),我需要先执行以下操作,将外部访问令牌交换为我自己的服务中的本地访问令牌:
- 与外部提供商进行身份验证(在移动应用程序中)
- 将提供者访问令牌发送到我的服务器(Web Api 2)
- 使用提供者验证访问令牌以确定用户是否合法 - 在接收ProviderKey的过程中(Facebook 和 google 中的 userId)
- 使用步骤 3中的Provider Name和UserId在本地登录用户
- 为当前登录的用户生成本地访问令牌。
Facebook 目前在网络和移动应用程序上完成上面列出的整个过程。使用 Microsoft,我可以在移动应用程序上进行身份验证并获取 Microsoft 访问令牌,但是,我被困在第 3 步,验证服务器上的访问令牌以接收可用于识别应用程序中已注册用户的任何有用信息。
为了识别正确的用户,我需要 AspNetUserLogins 表中的Login Provider 和 ProviderKey。
收到 Microsoft 访问令牌后,如何验证令牌并检查匹配的提供程序名称和提供程序密钥?
这是外部身份验证的通用/标准方法吗?如果是这样的话,我认为会有更多关于它的信息。非常感谢所有和任何帮助。
额外信息
我还尝试使用新的 Microsoft Graph Api(统一的 office365)支持普通的 Microsoft 帐户和工作/学校帐户。因此,根据 Microsoft/组织帐户,似乎有不同的提供程序密钥,例如https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/v2.0
代码示例
第 3 步 - 验证外部访问令牌以获取用户登录信息
Private async Task<ParsedExternalAccessToken> VerifyExternalAccessToken(string provider, string accessToken) {
ParsedExternalAccessToken parsedToken = null;
var verifyTokenEndPoint = "";
if (provider == Resources.Constants.FacebookProvider) {
//You can get it from here: https://developers.facebook.com/tools/accesstoken/
//More about debug_tokn here: https://stackoverflow.com/questions/16641083/how-does-one-get-the-app-access-token-for-debug-token-inspection-on-facebook
var appToken = WebConfigurationManager.AppSettings["fb_app_token"];
verifyTokenEndPoint = string.Format("https://graph.facebook.com/debug_token?input_token={0}&access_token={1}", accessToken, appToken);
} else if (provider == Resources.Constants.GoogleProvider) {
verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
} else if (provider == "Microsoft" || provider == Resources.Constants.MicrosoftAccountProvider || provider == Resources.Constants.MicrooftSchoolOrWorkAccountProvider) {
//made up end point -> what is the real answer/solution?
verifyTokenEndPoint = string.Format("https://login.microsoftonline.com/common/v2.0/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 == Resources.Constants.FacebookProvider) {
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 == Resources.Constants.GoogleProvider) {
parsedToken.user_id = jObj["user_id"];
parsedToken.app_id = jObj["audience"];
if (!string.Equals(Startup.googleAuthOptions.ClientId, parsedToken.app_id, StringComparison.OrdinalIgnoreCase)) {
return null;
}
} else if (provider == Resources.Constants.MicrosoftAccountProvider || provider == Resources.Constants.MicrooftSchoolOrWorkAccountProvider) {
throw new NotImplementedException("Microsoft Access Token Validation not implemented");
}
}
return parsedToken;
}
第 4 步 - 获取本地访问令牌
public async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken) {
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(externalAccessToken)) {
return ApiErrorResult(ServiceResults.ExternalAuth.Codes.ProviderOrExternalAccessTokenIsNotSent, ServiceResults.ExternalAuth.Messages.ProviderOrExternalAccessTokenIsNotSent);
}
var verifiedAccessToken = await VerifyExternalAccessToken(provider, externalAccessToken);
if (verifiedAccessToken == null) {
return ApiErrorResult(ServiceResults.ExternalAuth.Codes.InvalidProviderOrExternalAccessToken, ServiceResults.ExternalAuth.Messages.InvalidProviderOrExternalAccessToken);
}
var user = await _userManager.FindAsync(new UserLoginInfo(provider, verifiedAccessToken.user_id));
bool hasRegistered = user != null;
if (!hasRegistered) {
return ApiErrorResult(ServiceResults.ExternalAuth.Codes.ExternalUserIsNotRegistered, ServiceResults.ExternalAuth.Messages.ExternalUserIsNotRegistered);
}
//generate access token response
var accessTokenResponse = await GenerateLocalOauthToken(user);
return Ok(accessTokenResponse);
}
谢谢
更新
我已经尝试实现这两个@dstrockis 选项,但效果有限。在这两个选项上,我仍然无法弄清楚使用什么来匹配ProviderKey
在对 GET User 进行图形 Api 调用时,没有任何值与存储的 ProviderKey 匹配,例如 Facebook 和 Google 中使用的 userId。Microsoft ProviderKey 值如下所示:AAAAAAAAAAAAAAAAAAAAAAAADWmHuzvvAQpO*******9PM。
我还设法按照建议使用 JWT 验证库来验证访问令牌。查看声明,我现在可以识别正确的LoginProvider(良好的开始),但我仍然找不到任何与 ProviderKey 中存储的值匹配的值。
该值必须与用户有关,但不直接匹配任何内容。有谁知道 ProviderKey 来自 Microsoft OpenIdAuthentication 的来源。它是从另一个值加密的吗?
验证 JWT 令牌
private async Task ValidateMicrosoftToken(string token) {
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
TokenValidationParameters validationParameters = new TokenValidationParameters {
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningTokens = config.SigningTokens,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
//result contains claims with lots of values
//get provider key from claims????
}
身份验证提供程序 - Startup.Auth
microsoftAccountAuthOptions = new OpenIdConnectAuthenticationOptions() {
Description = new AuthenticationDescription() { AuthenticationType = "OpenIdConnect", Caption = "Microsoft"},
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
ClientId = appId,
Authority = authority,
Scope = "openid user.read email " + string.Join(" ", scopes),
RedirectUri = redirectUri,
//PostLogoutRedirectUri = "/",
TokenValidationParameters = new TokenValidationParameters {
//.....
},
Notifications = new OpenIdConnectAuthenticationNotifications {
//.....
}
};
app.UseOpenIdConnectAuthentication(microsoftAccountAuthOptions);