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 28, 2023
1 parent b10a14f commit c69435d
Show file tree
Hide file tree
Showing 38 changed files with 1,408 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
94 changes: 92 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>> 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,95 @@ public class CustomTokenStateManager implements TokenStateManager {
----
//SJ: In next iteration, propose to recompose Logout information into a new concept topic

[[db-token-state-manager]]
===== Stateful token management

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 exactly one xref:reactive-sql-clients.adoc[Reactive SQL client].
Following Reactive SQL clients are supported:

* Reactive MS SQL client
* Reactive MySQL client
* Reactive PostgreSQL client
* Reactive Oracle client
* Reactive DB2 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.

Let's say you already have application that is using the Hibernate ORM extension together with a PostgreSQL JDBC Driver and your datasource is configured like this:

[source, properties]
----
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
----

Now, if you decided to use OIDC Database Token State Manager, you need to add following dependencies and set a reactive driver URL.

[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
.pom.xml
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
----

[source, properties]
----
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_test
----

And you are ready to go.

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") <1>
@Entity
public class OidcDbTokenStateManagerEntity {
@Id
String id;
@Column(name = "id_token", length = 4000) <2>
String idToken;
@Column(name = "refresh_token", length = 4000)
String refreshToken;
@Column(name = "access_token", length = 4000)
String accessToken;
@Column(name = "expires_in")
Long expiresIn;
}
----
<1> The Hibernate ORM extension will only create this table for you when database schema is generated. Please refer to the xref:hibernate-orm.adoc[Hibernate ORM] guide for more information.
<2> You can choose column length depending on the length of your tokens.

==== 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 c69435d

Please sign in to comment.