Wednesday, 9 August 2017

Using PKCE with IdentityServer from a Xamarin Client

The OpenID Connect and OAuth 2.0 specifications define a number of authentication flows between clients and authentication providers. These include:

  • Implicit.This authentication flow is optimized for browser-based apps. All tokens are transmitted via the browser.
  • Authorization code. This authentication flow provides the ability to retrieve tokens on a back channel, as opposed to the browser front channel, while also supporting client authentication.
  • Hybrid. This authentication flow is a combination of the implicit and authorization code flows. The identity token is transmitted via the browser channel and contains the signed protocol response along with other artifacts such as the authorization code. After successful validation of the response, the back channel is used to retrieve the access and refresh tokens.

The eShopOnContainers mobile app communicates with an identity microservice, which uses IdentityServer 4 to perform authentication, and access control for APIs. The app uses the hybrid authentication flow to retrieve access tokens, as this flow mitigates a number of attacks that apply to the browser channel, and this approach is explained in the guidance documentation.

However, OAuth 2.0 clients that utilize authorization codes are susceptible to an authorization code interception attack. In this attack, the authorization code returned from an authorization endpoint is intercepted within a communication path not protected by Transport Layer Security (TLS), such as inter-application communication within the client’s operating system. Once the attacker has gained access to the authorization code, it can be used to obtain the access token. While a number of pre-conditions must hold for the authorization code interception attack to work, it has been observed in the wild.

To mitigate this attack, the Proof Key for Code Exchange (PKCE) extension to OAuth 2.0 adds additional parameters to the OAuth 2.0 authorization and access token requests:

  1. The client creates a cryptographically random key called a code verifier, and derives a transformed value, called a code challenge, which is sent in the OAuth 2.0 authorization request along with the transformation method.
  2. The authorization endpoint responds as usual but records the code challenge and transformation method.
  3. The clients sends the authorization code in the access token request, and also includes the code verifier.
  4. The authorization server transforms the code verifier and compares it to the code challenge. Access is denied if they are not equal.

This works as a mitigation for native apps because if an attacker intercepts the authorization code in step (2), it can’t redeem it for an access token as the attacker is not in possession of the code verifier. In addition, the code verifier can’t be intercepted since it’s sent over TLS.

For detailed information about PKCE, see Proof Key for Code Exchange by OAuth Public Clients.

Implementing PKCE

Following the guidance in the OAuth 2.0 for Native Apps specification, that PKCE should be used in authorization code based authentication flows, I’ve recently updated the eShopOnContainers mobile app to use PKCE when communicating with IdentityServer.

Server Side

IdentityServer must be configured to require the use of PKCE. This is achieved by modifying the configuration of the IdentityServer Client object for the Xamarin client:

public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl) { return new List<Client> { ... new Client { ClientId = "xamarin", ClientName = "eShop Xamarin OpenId Client", AllowedGrantTypes = GrantTypes.Hybrid, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { clientsUrl["Xamarin"] }, RequireConsent = false, RequirePkce = true, PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "orders", "basket" }, AllowOfflineAccess = true, AllowAccessTokensViaBrowser = true }, ... }; }

This configuration adds the RequirePkce property to the Client object. The RequirePkce property specifies whether clients using an authorization code must send a proof key.

Client Side

The CreateAuthorizationRequest method in the IdentityService class creates the URI for IdentityServer’s authorization endpoint, and the URI must be modified to include additional query parameters. The following code example shows the modified method:

public string CreateAuthorizationRequest() { // Create URI to authorization endpoint var authorizeRequest = new AuthorizeRequest(GlobalSetting.Instance.IdentityEndpoint); // Dictionary with values for the authorize request var dic = new Dictionary<string, string>(); dic.Add("client_id", GlobalSetting.Instance.ClientId); dic.Add("client_secret", GlobalSetting.Instance.ClientSecret); dic.Add("response_type", "code id_token"); dic.Add("scope", "openid profile basket orders locations marketing offline_access"); dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback); dic.Add("nonce", Guid.NewGuid().ToString("N")); dic.Add("code_challenge", CreateCodeChallenge()); dic.Add("code_challenge_method", "S256"); // Add CSRF token to protect against cross-site request forgery attacks. var currentCSRFToken = Guid.NewGuid().ToString("N"); dic.Add("state", currentCSRFToken); var authorizeUri = authorizeRequest.Create(dic); return authorizeUri; }

The client first creates a code verifier for the authorization request, with the CreateCodeChallenge method:

private string CreateCodeChallenge() { _codeVerifier = RandomNumberGenerator.CreateUniqueId(); var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); var challengeBuffer = sha256.HashData( CryptographicBuffer.CreateFromByteArray(Encoding.UTF8.GetBytes(_codeVerifier))); byte[] challengeBytes; CryptographicBuffer.CopyToByteArray(challengeBuffer, out challengeBytes); return Base64Url.Encode(challengeBytes); }

The CreateUniqueId method in the RandomNumberGenerator class creates a high-entropy cryptographic random string using the PCLCrypto library. Note that the PKCE specification requires that the code verifier is base64 URL-encoded to produce a URL safe string. However, the code verifier here is already URL safe, and so this additional operation isn’t required.

The CreateCodeChallenge method then creates a code challenge derived from the code verifier. This can be achieved by using one of the following transformations:

  • code challenge = code verifier (known as the plain transformation)

OR

  • code challenge = base64urlencode(Sha256(code_verifier)) (known as the S256 transformation)

If the client is capable of using the S256 transformation, it must do so, as this transformation is mandatory to implement on compliant servers. The CreateAuthorizationRequest method uses the S256 transformation, which SHA256 encodes the code verifier, and then base64 url-encodes the SHA256 output.

The client then sends the code challenge as part of the OAuth 2.0 authorization request, using the following additional parameters:

  • code_challenge – the derived code challenge
  • code_challenge_method – S256 (or plain)

When IdentityServer issues the authorization code in the authorization response, it associates the code challenge and code challenge method values with the authorization code so that it can be verified later. Note that if IdentityServer is configured to use PKCE, and the client does not send the code challenge, the authorization endpoint responds with an error response set to invalid_request.

Upon receipt of the authorization code, the client sends the access token request to the token endpoint. In addition to the existing parameters, it also sends the following parameter:

  • code_verifier – the code verifier

In the eShopOnContainers app, this is achieved with the GetTokenAsync method in the IdentityService class:

public async Task<UserToken> GetTokenAsync(string code) { var data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}&code_verifier={2}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback), _codeVerifier); var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret); return token; }

Upon receipt of the request at the token endpoint, IdentityServer verifies it by calculating the code challenge from the received code verifier, and comparing it with the previously associated code challenge, after first transforming it according to the code challenge method specified by the client. If the values are not equal, an error response indicating invalid_grant is returned. If the values are equal, the token endpoint continues processing as normal and responds with an access token, identity token, and refresh token.

Summary

OAuth 2.0 clients that utilize authorization codes are susceptible to an authorization code interception attack. To mitigate this attack, the PKCE extension to OAuth 2.0 adds additional parameters to the OAuth 2.0 authorization and access token requests.

Following the guidance in the OAuth 2.0 for Native Apps specification, that PKCE should be used in authorization code based authentication flows, I’ve recently updated the eShopOnContainers mobile app to use PKCE when communicating with IdentityServer.

For detailed information about PKCE, see Proof Key for Code Exchange by OAuth Public Clients.

No comments:

Post a comment