Skip to content

Commit

Permalink
Introduce OIDC Databse Token State Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed Sep 27, 2023
1 parent 49c5fdb commit 96dbeff
Show file tree
Hide file tree
Showing 38 changed files with 1,417 additions and 3 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,16 @@
<artifactId>quarkus-oidc-token-propagation-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-token-propagation-reactive</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,9 @@ public interface Capability {

String CACHE = QUARKUS_PREFIX + ".cache";
String JDBC_ORACLE = QUARKUS_PREFIX + ".jdbc.oracle";
String REACTIVE_PG_CLIENT = QUARKUS_PREFIX + ".reactive-pg-client";
String REACTIVE_ORACLE_CLIENT = QUARKUS_PREFIX + ".reactive-oracle-client";
String REACTIVE_MYSQL_CLIENT = QUARKUS_PREFIX + ".reactive-mysql-client";
String REACTIVE_MSSQL_CLIENT = QUARKUS_PREFIX + ".reactive-mssql-client";
String REACTIVE_DB2_CLIENT = QUARKUS_PREFIX + ".reactive-db2-client";
}
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-token-propagation</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-token-propagation-deployment</artifactId>
Expand Down
53 changes: 51 additions & 2 deletions docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,8 @@ In such cases, use the `quarkus.oidc.token-state-manager.strategy` property to c

If your chosen cookie strategy combines tokens and generates a large session cookie value that is greater than 4KB, some browsers might not be able to handle such cookie sizes.
This can occur when the ID, access, and refresh tokens are JWT tokens and the selected strategy is `keep-all-tokens` or with ID and refresh tokens when the strategy is `id-refresh-token`.
To workaround this issue, you can set `quarkus.oidc.token-state-manager.split-tokens=true` to create a unique session token for each token.
To workaround this issue, you can set `quarkus.oidc.token-state-manager.split-tokens=true` to create a unique session token for each token.
Another way to workaround this issue is to store tokens in a database. For more information, please see the <<db-token-state-manager,Storing token state in a database>> section of this guide.

`TokenStateManager` encrypts the tokens before storing them in the session cookie.
The following example shows how you configure `TokenStateManager` to split the tokens and encrypt them:
Expand Down Expand Up @@ -574,7 +575,7 @@ Otherwise, a random key is generated, which can be problematic if the Quarkus ap
You can disable token encryption in the session cookie by setting `quarkus.oidc.token-state-manager.encryption-required=false`.

Register your own `io.quarkus.oidc.TokenStateManager' implementation as an `@ApplicationScoped` CDI bean if you need to customize the way the tokens are associated with the session cookie.
For example, you may want to keep the tokens in a database and have only a database pointer stored in a session cookie.
For example, you may want to keep the tokens in a cache cluster and have only a key stored in a session cookie.
Note that this approach might introduce some challenges if you need to make the tokens available across multiple microservices nodes.

Here is a simple example:
Expand Down Expand Up @@ -633,6 +634,54 @@ public class CustomTokenStateManager implements TokenStateManager {
----
//SJ: In next iteration, propose to recompose Logout information into a new concept topic

[[db-token-state-manager]]
===== Storing tokens in a database

For simple applications, it can be useful to store tokens in a database, avoiding having to encrypt them in cookies.
To use this feature, add the following extension to your project:

:add-extension-extensions: oidc-db-token-state-manager
include::{includes}/devtools/extension-add.adoc[]

This extension will replace the default cookie-based `io.quarkus.oidc.TokenStateManager' with a database-based one.
For authentication is likely to happen on IO thread, the OIDC Database Token State Manager is using a Reactive SQL client under the hood.
Depending on your database, please include and configure respective xref:reactive-sql-clients.adoc[Reactive SQL client].

IMPORTANT: By no means your application code needs to use the Reactive SQL client. You can keep using the Hibernate ORM extension with your JDBC driver extension, while the extension will rely on the reactive one.

By default, a database table used for storing tokens is created for you, however you can disable this option with the `quarkus.oidc-db-token-state-manager.create-database-table-if-not-exists` configuration property.
Should you want the Hibernate ORM extension to create this table instead, you simply need to include an Entity like this one:

[source, java]
----
package org.acme.manager;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Table(name = "oidc_db_token_state_manager")
@Entity
public class OidcDbTokenStateManagerEntity {
@Id
String id;
@Column(name = "id_token", length = 4000)
String idToken;
@Column(name = "refresh_token", length = 4000)
String refreshToken;
@Column(name = "access_token", length = 4000)
String accessToken;
@Column(name = "expires_in")
Long expiresIn;
}
----

==== Logout and expiration

There are two main ways for the authentication information to expire: the tokens expired and were not renewed or an explicit logout operation was triggered.
Expand Down
99 changes: 99 additions & 0 deletions extensions/oidc-db-token-state-manager/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-oidc-db-token-state-manager-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-oidc-db-token-state-manager-deployment</artifactId>
<name>Quarkus - OpenID Connect Database Token State Manager - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-deployment</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>test-keycloak</id>
<activation>
<property>
<name>test-containers</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
<systemPropertyVariables>
<mssql.image>${mssql.image}</mssql.image>
<db2.image>${db2.image}</db2.image>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.oidc.db.token.state.manager;

import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

@ConfigMapping(prefix = "quarkus.oidc-db-token-state-manager")
@ConfigRoot
public interface OidcDbTokenStateManagerBuildTimeConfig {

/**
* Whether token state should be stored in the database.
*/
@WithDefault("true")
boolean enabled();

}
Loading

0 comments on commit 96dbeff

Please sign in to comment.