使用Open Identity Connect和OAuth在Xamarin的身份验证

让我们谈谈使用OpenID和OAuth中的Xamarin表单应用程序的验证用户。在忘怀的人谈论身份验证时,它几乎感觉就像有一个神秘的东西,几乎像一个秘密俱乐部,与外人不知道。至少,这就是我如何记住它的时候,我开始向我当时正在处理的应用程序添加身份验证层。但与许多事情一样,您投资的时间越多,越多的谜团似乎似乎是有意义的,在你知道之前,你正在通过认证相关的任务来巡航。现在身份验证不是一个新的概念,但它可能听到最好遵循标准。使用经过验证的图书馆而不实现自己的图书馆。我们将在移动Xamarin表单应用程序中使用OpenID / OAuth2使用OpenID / OAuth2进行身份验证。

OAuth2是A.标准提供不同流量的用户如何验证自己。使用移动应用程序时推荐的流量是代码流。如果我们在图表上绘制它,那看起来像这样:

OAuth代码流程图

您可以看到身份验证粗略分为四个步骤。

  1. 认证

  2. 用代码的回调到应用程序

  3. 请求使用回调的代码访问令牌(和身份令牌)

  4. 刷新令牌

在应用程序中,前三个步骤看起来像这样。

Xamarin表单应用程序中登录过程的动画屏幕流程。

您可能已经注意到用户使用浏览器窗口进行身份验证。这是意图。代码流不允许用户使用应用程序中的本机视图登录。作为这种流的原因可确保客户端从未见过用户名和密码(浏览器除了浏览器之外,这是OS系统的一部分 - AKA我们信任它)。您可以使用具有资源所有者密码凭据(ROPC)流的本机登录视图启用。但这也是攻击矢量。假设有人使您的申请复制欺诈重复,并欺骗用户进入其凭据。欺诈应用程序可以将这些凭据存储在一起。在做安全时,你必须享受那些锡箔帽的时刻。 ðÿ™ƒ换句话说,使用代码流不给攻击者提供机会,因此是移动客户端的推荐选项。

通过该简要介绍身份验证流程以及为什么建议使用代码流,让我们开始组装代码,将图表带到寿命。

验证用户

此步骤旨在获取一个代码,允许我们从Identity Server请求令牌。了解OAuth2规范从头开始构建此类客户端并不小壮举。幸运多米尼克拜耳布洛伦 have done a lot of heavy lifting for us on the client-side by providing us with the IdentityModel.OidcClient library. The library offers the OidcClient class, which will significantly simplify the calls to authenticate our user. We will require a few parameters to make the request:

范围 描述
权威 身份服务器的URL。
ClientId. 客户的ID。
范围 he scopes you want to be included in the tokens. OpenId includes the scopes openidprofile by default. Another one is offline, which will request a refresh token from the backend—more on the refresh token in a bit.
redirecturl. 登录表单将在登录成功后转发给此URL。我们将在一秒内仔细查看此参数。
clielateecret A secret that the client and server share. Since storing secrets in a mobile app are usually discovered relatively easy after unzipping and decompiling them. Regard this parameter as optional (no additional security benefits) for mobile apps. If, however, the server has defined a secret (usually a string), you have to set it on the client. The undefined value is null, which is also the default value and used later on.

上述参数通常与应用程序不同于应用程序。他们甚至可能因环境而异,即分段与生产。参数在Identity Server上定义,例如,后端并且必须在客户端上相应地插入。看看我的下一个Blogpost.关于如何为Xamarin.Forms项目设置IdentityServer。

权威: This URL points to the root URL of the identity server. The OidcClient library knows by a convention which endpoints it has to call for the authentication and requesting the tokens later on by calling the //path-to-the-identity-server.ch/.well-known/openid-configuration.

With the parameters at hand, we can create a new OidcClient和pass in the parameters via an OidcClientOptions object as follows:

private OidcClient CreateOidcClient()
{
    var options = new OidcClientOptions
    {
        权威 = _authorityUrl,
        ClientId. = _clientId,
        范围 = _scope,
        RedirectUri = _redirectUrl,
        clielateecret = _clientSecret,
        Browser = new WebAuthenticatorBrowser()
    };

    var oidcClient = new OidcClient(options);
    return oidcClient;
}

There is one additional parameter that did not get mentioned before: The Browser parameter. Since the code flow will open a browser window for the user to log in, we need to configure the browse. Luckily we can use the Web Authenticator from the Xamarin.Essentss.包装将提供完整的此功能。有一些入门步骤 required, which you find in the official Microsoft docs. To invoke the All that we have to do is embed it in an IBrowser interface provided by the OidcClient library:

internal class WebAuthenticatorBrowser : IBrowser
{
    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
    {
        try
        {
            WebAuthenticatorResult authResult =
                await WebAuthenticator.认证Async(new Uri(options.StartUrl), new Uri(options.EndUrl));
            var authorizeResponse = ToRawIdentityUrl(options.EndUrl, authResult);

            return new BrowserResult
            {
                Response = authorizeResponse
            };
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
            return new BrowserResult()
            {
                ResultType = BrowserResultType.UnknownError,
                Error = ex.ToString()
            };
        }
    }

    public string ToRawIdentityUrl(string redirectUrl, WebAuthenticatorResult result)
    {
        IEnumerable<string> parameters = result.Properties.Select(pair => $"{pair.Key}={pair.Value}");
        var values = string.Join("&", parameters);

        return $"{redirectUrl}#{values}";
    }
}

The main interaction point is the InvokeAsync method. Here we use the Web Authenticator to open a browser. The end url passed to the server is the redirecturl. we defined earlier in the options. This is a deep link back into your application, which is also part of the configuration of the Xamarin.Essentials Web Authenticator functionality. The response is a WebAuthenticatorResult. We are parsing the result into an HTTP request parameter string which we will use to request the tokens. We add the request string to the browser result. If anything goes wrong, we will return an error in the same object.

在所有此配置之后,我们可以通过调用以下代码行来启动身份验证过程:

OidcClient oidcClient = CreateOidcClient();
LoginResult loginResult = await oidcClient.LoginAsync(new LoginRequest());

Starting the authentication will invoke our browser implementation WebAuthenticatorBrowser. Should the browser return a result, the OidcClient will automatically request the tokens from the server. If, however, there was an error at any stage, the result would provide us with the information. I.e. an exception occurred while executing the browser code.

从服务器返回的令牌是访问令牌,ID令牌,如果请求刷新令牌。 ID令牌是JSON Web令牌(jwt.)可以被视为来自身份服务器的响应。您可以使用JWT Parser查看令牌的内容一。值得注意的是,虽然访问令牌也是一个JWT令牌,并且可以解析OAuth规范没有定义它。最好不要依赖于访问令牌中的信息作为客户端,但要更好地从ID令牌中读取它,您可以并应将其视为身份服务器的响应。例如,ID令牌将包含当访问令牌到期时的信息。

进行经过身份验证的请求

Given a valid response from the server. Add the access token to a HttpClient header:

_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", credentials.AccessToken);

Every call using this HttpClient will now provide the server with an access token. Should you want to remove the authentication header from your client, you can set it to null.

_httpClient.DefaultRequestHeaders.Authorization = credentials.IsError
    ? null
    : new AuthenticationHeaderValue("bearer", credentials.AccessToken);

Setting the value to null will also remove it from the request header of the HttpClient.

刷新访问令牌

访问令牌通常仅在短时间内有效。多长时间取决于服务器设置。客户端收到访问令牌到期时的日期和时间。通过解析id令牌(这是一个jwt令牌), or we get a handy property AccessTokenExpiration on the response. The default with the identity server is 60 minutes. This would imply that the user will have to go through the authentication every hour. Do you remember a mobile app demanding this of you? Probably not. And if there were, you would probably be looking for an alternative. Luckily there is a way to prevent your user from having to provide the credentials over and over again. You can request a refresh token by adding the offline_access scope (this has to be allowed by the server). Then you can request a new access token from the server without asking the user for any information:

OidcClient oidcClient = CreateOidcClient();
RefreshTokenResult refreshTokenResult = await oidcClient.RefreshTokenAsync(refreshToken);

在请求新的访问令牌时,您还将收到一个新的刷新令牌和标识令牌。刷新令牌的生命周期定义(再次)在服务器上。刷新令牌的默认寿命使用Identity Server为30天。因此,如果用户定期使用该应用程序,则不需要登录。但是,如果您的应用程序不接受它应该的爱,则每次都会使用登录掩码面对用户。

存储令牌

到目前为止,我们已在用户登录后收到令牌。此外,我们看着更新Towe也看到了如何拍摄,没有用户侧的任何互动的kens。这种基于令牌的方法是您的应用程序永远不会负责处理用户的凭据。然而,存储令牌的可能有意义,即,确保在重新启动应用程序后不必登录用户。由于令牌模拟了用户,我们应该考虑将它们保持在安全的位置。再次Xamarin.Essentss图书馆将具有很大的帮助,更准确地说,安全存储图书馆的功能。它使应用程序能够存储加密的信息或取决于电话和平台,即使在安全存储模块上也是如此。

搭档

我希望身份验证失去了一些神秘。此过程首先可能看起来有点复杂,但它是为了确保我们的用户的凭证不会陷入错误的手中。我们还看到了oidcclient和xamarin.Exessentials有准备使用库,并帮助在Xamarin应用程序中实现开放ID / OAuth2标准。

您可以找到实现上面所描述的步骤的示例应用程序GitHub..

Hth.

更新: