Most software developers learn OpenID Connect or other identity standards by chance. Some will love and others may hate that experience. Regardless of their affection to the standards, it’s crucial that software engineers understand reasonably well how these standards work and what their implications are, in terms of usability but especially in terms of security. Just finding a library and making it work is a recipe for failure.

What is the most common task, the problem

The most common task a developer faces in which OpenID Connect comes into place is: integrate our application with an authentication method (e.g. login with Office 365, bank authentication), so as a result, this new authentication method is enabled in the application. In quora.com or stackoverflow.com you will easily find articles starting with “how to integrate openid connect …”

The problem to solve is the following: unless OpenID Connect was thought of from the very first moment an application was designed, the application cannot use an external authentication method such as “Login with Apple” so add a login button besides its own username+password boxes.

In order to capitalise on the numerous advantages of adding an external authentication method, the application has to become an OpenID Connect relying party. In practice, the developer must embed such functionality into the application. The most common way to do it is by using libraries approved by The OpenID Foundation.

The OpenID Connect standard has different flows for different use cases that involve user authentication, and the most common flow used today for both web and mobile applications is authorisation code flow. The next step besides flow choice is to define how the client authentication will happen.

Types of clients

RFC 6749 defines type of clients in Section 2.1: OAuth 2.0 Client Types as show below.

OAuth defines two client types, based on their ability to authenticate securely with the authorization server (i.e., ability to maintain the confidentiality of their client credentials):

Confidential

Clients capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means.

Public

Clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means.

When would you need to use a confidential client? Possible reasons are for example:

  • It is a policy requirement defined by the Identity Provider
  • The claims returned by the Identity Provider could be highly confidential
  • There could be a cost associated with each authentication transaction

Now that we understand types of clients better, we can see that most of today’s real life scenarios require confidential clients. Broadly speaking there are two types of credentials for confidential clients:

  • Symmetric credentials using any of the client_secret authentication options
  • Asymmetric credentials using either private_key_jwt or mutual TLS authentication options

Confidential clients must send client credentials with requests to endpoints that require authentication, which includes token, introspection and revocation endpoints.

Today for most scenarios, the integration of applications is done with OpenID Connect using client_id and client_secret. However private_key_jwt is another option that often comes to developer’s attention.

This is when the question “Should I use private_key_jwt or client_secret?” comes into play.

Should I use private_key_jwt or client_secret?

First of all, it’s good to clarify that there are three flavours of client_secret. Let’s review each by one:

  1. On client_secret_basic, the client uses HTTP Basic authentication scheme with client_id and client_secret.
  2. On client_secret_post, the client sends client_id and client_secret as HTML form parameters
  3. On client_secret_jwt, the client uses JWTs for client authentication. The JWT is signed with a key derived from client_secret.

The first two are very similar and only differ on how the client_secret is sent in the POST (or GET) calls. The most common one is client_secret_basic, while client_secret_jwt is very rarely used.

Now let’s analyse how client_secret works.

Let’s imagine we have an OpenID Connect compliant application, which wants to allow users to authenticate with an external identity provider such as Login with GitHub. The very first step is to check what the type of client authentication used is. In case of GitHub, client_secret is used.

In this scenario, GitHub is the identity provider as all the user accounts (and its respective credentials) are stored on its servers. When you, as a developer, want to integrate GitHub login to an application, you must go to GitHub developer portal to register an OAuth 2.0 application. In that process, GitHub will generate a client_id and client_secret for this new client.

The developer copies this piece of information on the relying party’s CIAM system, and the registration is completed. For reference, step-by-step instructions to do this on Ubisecure Identity Platform are here.

Please observe that:

  • The identity provider has generated the secret.
  • Every time the application wants to perform client authentication to the server, first it must send the client_secret through the channel in order to obtain an access token.

Let’s compare those similar steps to analyse how private_key_jwt works.

Now imagine the OpenID Connect compliant application wants to allow users to authenticate with an external identity provider like the Finnish Trust Network. In this case, private_key_jwt is the only allowed option for client authentication.

When you as the developer want to integrate Finnish Trust Network login to an application, you have to follow an identity broker’s integration guide. The identity broker has publicly shared all its relevant metadata on the .well-known/openid-configuration endpoint. This includes its JSON Web Encryption keys (jwks_uri). For instance, Telia Identification Broker Service’s jwks_uri is https://tunnistus.telia.fi/uas/oauth2/metadata.jwks. In the registration process, the relying party must generate its own pair of asymmetric keys, typically throughout its CIAM system.

Following OpenID Connect Dynamic Client Registration 1.0, a “registration request” is sent to the identity provider (identity broker strictly speaking for Telia) and the service provider will receive back a “registration response” metadata file. The developer uploads this metadata on the relying party’s CIAM system, and the registration is completed.

Notice that:

  • Both the relying party and the identity provider generate a pair of asymmetric keys on their own side.
  • Every time the application wants to authenticate, no secret is sent through the channel. Instead, the private key is used to sign the request so a signed client_assertion is sent. The other end can use the sender’s public key to decrypt the assertion.

If you are unsure which one(s) of these two are supported by the identity provider you want to integrate, the OpenID Connect discovery endpoint of a server (.well-known/openid-configuration) shows the list of client authentication methods supported.

Comparison table

The table below offers a succinct comparison between the two client authentication options.

Client_secret Private_key_jwt
A secret is shared No secret is shared
Based on symmetric cryptographic Based on asymmetric cryptography, which offers stronger algorithms
Secret is usually generated on the Identity provider side, and then shared to the relying party in order to integrate the specific application Key pairs are generated by the service provider side (using the Identity and Access Management system), and only the public key is shared to the identity provider
It’s used today in the vast majority of identity providers, including all social media It’s a requirement for Open Banking APIs under the Financial Grade API (FAPI), the Finnish Trust Network, and other similar profiles.
Manual key rotation, based on a shared secret Dynamic key rotation

Overall, in the near future you will use both, especially client_secret. But as FAPI has adopted it because of its improved security, private_key_jwt is the way to go if you have the choice.