Securing Authentication Flows with JSON Web Encryption

We previously covered the fundamentals of JSON Web Encryption (JWE), an IETF standard designed to keep data confidential.
In today’s blog post, we’ll explore how JWE can help secure sensitive information specifically in OpenID Connect authentication flows.
FTN Security Requirements: What You Need to Know
The Finnish government is introducing new requirements to tighten authentication security of the Finnish Trust Network (FTN). The requirements are described in Recommendation 213/2023 S Finnish Trust Network OpenID Connect Profile and include:
- Private Key JWT client authentication
- Authentication request signing
- Encrypted token and userinfo responses
We've just implemented these changes at Criipto and are sharing the details with you in this article series. The goal is to explain the main concepts and demonstrate how these requirements enhance the overall security of authentication flows.
If you're integrating with FTN, you have to understand and implement these changes. Even if FTN is not among the eIDs you currently use, we encourage you to familiarize yourself with these concepts: similar security measures might be adopted by other eIDs in the future, or you might simply gain a better understanding of modern authentication security.
Encrypted token and userinfo responses can be implemented using JWE.
What is JWE? (A quick recap)
JSON Web Encryption (JWE) is a standard for encrypting data and representing it in JSON format.
JWE can be applied to JSON Web Tokens (JWTs) to ensure data confidentiality. Unlike standard JWTs, which guarantee integrity and authenticity but leave data readable, JWE encrypts the content so that only the intended recipient can access it. By combining JWE with JSON Web Signature (JWS), you can create a signed and encrypted token (nested JWT) that is usable as an access token in OAuth 2.0 or an ID token in OpenID Connect.
How it works in authentication flows
If you need a refresher on how authentication flows work, we have written another blog post about what happens when you log in with an eID via Criipto.
Now let’s look at the authentication flow in more detail to see how encrypted token responses change it and what the benefits are.
The standard authentication flow
Here’s how a standard authentication flow looks with an Identity Provider (IdP) like Danish MitID or FTN:
- After the end-user authenticates with an IdP, the IdP issues a JWT with the end-user's personal information and sends it to Criipto, which then:
- Stores the user’s data in our session store.
- Generates a code argument.
- Criipto redirects the end-user to the client application with the code argument.
- The client application calls the oauth2/token endpoint with the code argument.
- Criipto generates an id_token based on stored user data.
- Criipto saves user data in the session store (meant for the access_token and the userinfo endpoint).
- Criipto generates an access_token.
- Criipto responds to the client application with id_token and access_token.
- (Optional) The client application calls oauth2/userinfo with the access_token.
- Criipto generates userinfo based on the stored user data.
- Criipto responds to the client application with the user data.
In this flow, Criipto could decrypt the user data (or our session data) from any point after step #1.1.
Authentication flow with encrypted token and userinfo responses
A prerequisite is that your client application registers its public key with Criipto and securely keeps the corresponding private key of the asymmetric key pair.
- After the end-user authenticates with an IdP, the IdP issues a JWT with the end-user's personal information and sends it to Criipto. This token can also be encrypted (a requirement for FTN), in which case the IdP uses Criipto's public key for encryption.
- Criipto generates an id_token, encrypts it with the client application’s public key, and stores it.
- Criipto generates userinfo, encrypts it with the client application’s public key, and stores it.
- Criipto generates a code argument.
- Criipto redirects the end-user to the client application with the code argument.
- The client application calls the oauth2/token endpoint with the code argument.
- Criipto returns an encrypted response from our session store.
- Criipto generates an access_token.
- Criipto responds to the client application with id_token and access_token.
- (Optional) The client application calls oauth2/userinfo with the access_token.
- Criipto returns an encrypted response from our session store to the client application.
In this flow, Criipto can access data for a much shorter time by encrypting it as early as possible.
Enhanced security and GDPR compliance
Encrypting the token and userinfo responses with your public key as early as possible minimizes Criipto's access to sensitive user data.
One key benefit is improved GDPR compliance. In the standard flow, we store end-users' personal information for about four minutes and could potentially decrypt and view this data. With encryption, however, Criipto cannot access this data. Plus, even if our database were compromised, the data would remain confidential. Ultimately, this translates into better data protection and stronger GDPR compliance.