Improved OAuth public client support and PKCE availability in Connect2id server 3.9

Improved public client support

The latest release of the Connect2id server for OpenID Connect and OAuth token based security has its focus on public clients. These are commonly used in situations where the client software is not hosted on a secure server, as is the case with traditional web apps, but instead resides on a user device (smartphone) or is executed inside a browser (for JavaScript based apps). In user environments client credentials cannot reliably be kept confidential, even if you resort to complicated techniques, such as encrypting the client secret inside the app code. Because even with such encryption, a dedicated hacker can still be a able to extract the secret while it's decrypted in the app memory.

With such clients you have two options:

  • Still provision the client with a client_id and client_secret, as with regular confidential clients, but don't assume that the secret is confidential, and can therefore be relied upon to verify the client.

  • Don't provision the client with a secret at all.

The latest release of the Connect2id server allows you to register public clients explicitly, by simply setting the token endpoint authentication method to none:

POST /c2id/clients HTTP/1.1
Host: demo.c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "application_type"           : "native",
  "redirect_uris"              : [ "com.example.app:///auth" ],
  "token_endpoint_auth_method" : "none"
}

For a detailed explaination check out the public client registration how-to.

You can also check out the definition of public and confidential clients from the OAuth spec (RFC 6749) and the security considerations for client identification and authentication.

Authorising public clients

To aid consent for public clients, the authorisation session API was updated to indicate the type of the requesting client - whether it's confidential or public. This is done with help of the client.client_type parameter. Check out the consent prompt docs for more info.

Example consent prompt with the new client_type parameter:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "type"        : "consent",
  "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
  "display"     : "popup",
  "sub_session" : { "sid"           : "9yLXidrzk91r3BCpJeF1Vrf_aza4oNe-EdNkaXBa1iw",
                    "sub"           : "alice",
                    "auth_time"     : 12345678,
                    "creation_time" : 12345678,
                    "max_life"      : 20160,
                    "auth_life"     : 1440,
                    "max_idle"      : 15 },
  "client"      : { "client_id"        : "8cc2043",
                    "client_type"      : "public",
                    "application_type" : "native" 
                    "name"             : "My Example App" },
  "scope"       : { "new"       : [ "openid", "email" ],
                    "consented" : [ ] },
  "claims"      : { "new"       : { "essential" : [ "email", "email_verified" ],
                                    "voluntary" : [ ] },
                    "consented" : { "essential" : [ ],
                                    "voluntary" : [ ] } }
}

Note that tokens issued to public clients should not be granted sensitive scopes, and their lifetime should be limited. Refresh tokens should not be issued unless required. The authorisation session API allows you to override the global token settings on a individual basis. So you can easily apply a public client policy when a "public" client.client_type parameter is detected.

Also, keep in mind that incremental authorisation is not feasible for a public client which client_id is shared across multiple app instances (e.g. for an app that is downloaded from an app store).

If you need to be able to distinguish between app instances for the purpose of authorisation (or other needs), and it doesn't matter whether the client is confidential or public, consider dynamic registration, that is registering each app instance with the Connect2id server once it's activated so it can obtain its own client_id. Self-registration can be managed via a proxy in front of the client registration endpoint and / or by use of a software statement - a JWT signed by the software publisher that locks down the parameters for the client registration.

If you need help with that get in touch with Connect2id support.

Proof key for code exchange (PKCE)

Public OAuth clients that use the code grant and run on smartphones are susceptible to a code interception attack. Fortunately, this attack can be successfully prevented by establishing a secure binding between the authorisation request and the subsequent token request.

The OAuth work group devised an official mini extension of the protocol for that, called Proof Key for Code Exchange (PKCE) and published in September 2015 as RFC 7636.

How does PKCE work?

  • The client creates a large random string called the code verifier.

  • The client then computes its SHA-256 hash, called the code_challenge.

  • The client passes the code_challenge and code_challenge_method (a keyword for the SHA-256 hash) along with the regular authorisation request parameters to the Connect2id server. The server stores them until a token request is received by the client.

  • When the client receives the authorisation code, it makes a token request with the code_verifier included. The Connect2id server recomputes the code challenge, and if it matches the original one, releases the requested tokens.

PKCE essentially works by preventing a malicious app or code had intercepted the code (as it was passed from the system browser / the OS to the app) from exchanging it for a token.

The latest release of the Connect2id server adds complete support for PKCE. In order to make use of it a public client just needs to set the appropriate PKCE request parameters. The server will take care of the rest.

If you're using the Connect2id OAuth / OpenID Connect SDK for Java (v 5.4+) PKCE is easy:

import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.pkce.
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.*;

// Generate random code verifier
CodeVerifier codeVerifier = new CodeVerifier();

// Compute code challenge using SHA-256
CodeChallenge codeChallenge = CodeChallenge.compute(CodeChallengeMethod.S256, codeVerifier);

// Make OpenID request and include code challenge + method
URI authRequestURI = new AuthenticationRequest.Builder(
            new ResponseType("code"), Scope.parse("openid email"), clientID, redirectURI,
            .state(new State())
            .codeChallenge(codeChallenge, CodeChallengeMethod.S256)
            .endpointURI("https://demo.c2id.com/login")
            .build()
            .toURI();

// Continue as usual...

// Make token request, include code verifier
AuthorizationGrant grant = new AuthorizationCodeGrant( code, redirectURI, codeVerifier);
TokenRequest tokenRequest = new TokenRequest(URI.create("https://demo.c2id.com/token", clientID, grant);
HTTPResponse httpResponse = tokenRequest.toHTTPRequest().send();

To obtain the tokens the client needs to supply the correct code verifier. Malicious third-party software that has intercepted the callback would not be able to do that, and the code will be withheld with the following error message:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "error"             : "invalid_grant",
  "error_description" : "Invalid or expired authorization code, redirection URI mismatch, or PKCE verification failure"
}

Check out the superb JavaDocs if you need help with the SDK use.

Download

To download a ZIP package of Connect2id server 3.9:

https://connect2id.com/assets/products/server/download/3.9/Connect2id-server.zip

As WAR package only:

https://connect2id.com/assets/products/server/download/3.9/c2id.war https://connect2id.com/assets/products/server/download/3.9/c2id-3.9.war

Questions?

Please contact Connect2id support.


Connect2id Server 3.9 release notes

Configuration

  • /WEB-INF/oidcProvider.properties

    • Adds 'none' to the list of supported client authentication methods at the token endpoint (op.token.authMethods).

Web API

  • /clients

    • Enables explicit registration of public OAuth clients by setting the token_endpoint_auth_method parameter to 'none'.
  • /token

    • Adds support for Proof Key for Code Exchange (PKCE) to prevent authorisation code interception attacks on public OAuth clients.
  • /token/revoke

    • Public clients are permitted to revoke access and refresh tokens that have been issued to them.
  • /authz-sessions/rest/v2

    • Adds a "client_type" field with values "confidential" and "public" to the "client" object of authentication and consent prompts to indicate the OAuth client type.

Bug fixes

  • Permits client registration of custom URI schemas in addition to "http://localhost" for native applications (for application_type=native) (issue server/187).

  • Fixes unnecessary provisioning of client_secret for clients that are registered for the implicit grant only (with response types "id_token" or "token id_token" (issue server/184).

Dependencies

  • Upgrades to com.nimbusds:oauth2-oidc-sdk:5.4

  • Upgrades to com.nimbusds:oauth2-authz-store:3.5

  • Upgrades to org.bouncycastle:bcprov-jdk15on:1.54

Other

  • Improves the description of client registration invalid_redirect_uri error messages.

  • Changes the Connect2id server policy for logging the overriding system properties (if any) so that only the key names are written. This is done to prevent leaking of sensitive data into the server logs (issues authz-store/110 and server/186).