Authentification OIDC avec Quarkus

Quarkus a un module pour faire de l’authentification. La documentation explique comment l’utiliser avec Keycloak. J’ai du l’utiliser avec Strava.

Extension

Extension quarkus-oidc

  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId>
  </dependency>

Keycloak

Configuration

quarkus:
  oidc:
    auth-server-url: http://localhost:8180/realms/quarkus
    client-id: frontend
    application-type: web-app
    authentication:
      redirect-path: /tokens
  http:
    auth:
      permission:
        authenticated:
          paths: /*
          policy: authenticated

Avec cette configuration, une authentification est demandée pour n’importe quelle requête. Pour ça, la requête est redirigée vers Keycloak.

Keycloak doit être démarré au préalable et le realm fourni dans l’exemple doit être importé.

docker run --name keycloak -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:26.0.7 start-dev

Sans la propriété quarkus.oidc.auth-server-url, en dev, Quarkus démarre une instance de Keycloak, sous forme de Dev Service.

Transport par le cookie q_session.

Code

Il y a plusieurs possibilités d’injection.

  @Inject
  SecurityIdentity securityIdentity;
  • io.quarkus.security.identity.SecurityIdentity

    • isAnonymous(): boolean

    • getRoles(): Set<String>

    • getPrincipal(): Principal

    • getCredentials(): Set<Credential>

    • getCredential(Class<T> credentialType): T

    • getAttributes(): Map<String, Object>

    • getAttribute(String name): T

C’est assez générique, mais pas très lisible.

  @Inject
  IdTokenCredential idToken;

  @Inject
  AccessTokenCredential accessToken;

  @Inject
  RefreshToken refreshToken;
  • io.quarkus.oidc.SecurityIdentity

    • getToken(): String

    • getType(): String

    • isInternal(): boolean

  • io.quarkus.oidc.AccessTokenCredential

    • getToken(): String

    • getType(): String

    • isOpaque(): boolean

  • io.quarkus.oidc.RefreshToken

    • getToken(): String

    • getType(): String

Ça manque d’informations utilisables.

  @Inject @IdToken
  JsonWebToken idToken;

  @Inject
  JsonWebToken accessToken;

  @Inject
  JsonWebToken refreshToken;

Là on a le détail des tokens JWT. Ça marche parce que Keycloak fournit des tokens JWT.

Strava

Strava fait partie des fournisseurs prédéfinis, comme Google, Microsoft, Facebook,…​

quarkus:
  oidc:
    provider: strava
    client-id: 45678
    credentials:
      secret: 0123456789abcdef0123456789abcdef01234567
    application-type: web-app
    authentication:
      redirect-path: /tokens
  http:
    auth:
      permission:
        authenticated:
          paths: /*
          policy: authenticated

Pour les injections, on a des différences car les tokens fournis par Strava sont opaques. Et aucun id token n’est fourni. A la place, Quarkus en construit un en interne à partir des user info.

Si on injecte des JsonWebToken, une OIDCException est lancée, expliquant qu’un token opaque ne peut pas être converti en JWT.

io.quarkus.oidc.OIDCException: Opaque access token can not be converted to JsonWebToken
	at io.quarkus.oidc.runtime.OidcJsonWebTokenProducer.getTokenCredential(OidcJsonWebTokenProducer.java:67)
	at io.quarkus.oidc.runtime.OidcJsonWebTokenProducer.currentAccessToken(OidcJsonWebTokenProducer.java:41)

Pas de problème pour l’id token, qui est généré en interne, en JWT.

Personnalisation

Pour stocker les tokens en base de données: DatabaseTokenStateManager (extends TokenStateManager).

Pour personnaliser les rôles (par exemple avec les clubs): RolesAugmentor (extends SecurityIdentityAugmentor).

Pour les deux, on a la même impossibilité d’injecter des beans de scope request ou session. C’est dû au fait que le contexte de RequestScoped n’est pas encore actif.