使用ASP.NET WebApi 2的移动应用程序的Microsoft Graph外部身份验证

时间:2016-07-05 00:44:42

标签: asp.net-mvc asp.net-web-api owin microsoft-graph microsoft-account

我正在创建一个移动应用程序(Xamarin + MvvmCross),它可以访问我的服务 Web API 2 + Owin ,并且我正在尝试支持外部登录。对于以下内容,假设我已经使用外部身份验证注册了用户,并且只想从移动应用程序登录用户。

目前我的Facebook登录工作主要遵循ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app,但是经过几个小时后,我无法弄清楚如何为Microsoft登录实施相同的策略,因为我无法找到方法验证Microsoft访问令牌。

我的理解是,为了调用我自己的API(例如获取我的用户数据和对象),我需要首先执行以下操作,从我自己的服务中为本地访问令牌交换外部访问令牌:

  1. 使用外部提供商进行身份验证(在移动应用中)
  2. 将提供者访问令牌发送到我的服务器(Web Api 2)
  3. 在接收 ProviderKey (Facebook和google中的userId)的过程中,与提供商验证访问令牌,以确定用户是否合法?
  4. 使用步骤3中的提供商名称 UserId 在本地登录用户
  5. 为当前登录的用户生成本地访问令牌。
  6. Facebook目前正在通过网络和移动应用程序完成上述整个流程。使用Microsoft,我可以在移动应用程序上进行身份验证并获取Microsoft访问令牌,但是我坚持在第3步,验证我服务器上的访问令牌以接收我可用于识别我的应用程序中已注册用户的任何有用信息。

    为了识别正确的用户,我需要AspNetUserLogins表中的 Login Provider和ProviderKey

    收到Microsoft Access令牌后,如何验证令牌并检查匹配的提供商名称和提供商密钥?

    这是外部身份验证的常用/标准方法吗?如果是这样的话,我认为会有更多关于它的信息。非常感谢所有和任何帮助。

    额外信息

    我还尝试使用新的Microsoft Graph Api(统一office365)支持普通的Microsoft帐户以及工作/学校帐户。因此,根据Microsoft / Organization帐户,似乎存在不同的提供者密钥,例如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用户进行图表Api调用时,没有值与存储的ProviderKey匹配,例如Facebook和Google中使用的userId。 Microsoft ProviderKey值如下所示:AAAAAAAAAAAAAAAAAAAAADWmHuzvvAQpO ******* 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????
            }
    

    Auth Provider - 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);
    

1 个答案:

答案 0 :(得分:0)

这种方法非常好。对于第3步,您确实有两个选择:

祝你好运!