OAuth2 Client

Ce plugin fournit à SPIP une implémentation native du protocole OAuth2 ainsi que de son extension OpenID Connect.
Il permet à un site SPIP d’agir
-  comme client d’un serveur d’autorisation OAuth2,
-  comme client d’un fournisseur d’identité OpenID Connect
-  ou comme client d’une API métier protégée par OAuth2.

L’implémentation est autonome et ne repose sur aucune bibliothèque tierce (ni Composer, ni league/oauth2-client...). Il constitue essentiellement une brique technique destinée à être utilisée par d’autres plugins qui, eux, exposent une API.

Ce plugin fournit à SPIP une implémentation native du protocole OAuth2 (RFC 6749) ainsi que de son extension OpenID Connect (Core 1.0). Il permet à un site SPIP d’agir comme:

  • un client d’un serveur d’autorisation OAuth2
  • un client d’un fournisseur d’identité OpenID Connect (Keycloak...)

L’implémentation est autonome et ne repose sur aucune bibliothèque tierce (ni Composer ni league/oauth2-client). Il constitue une brique technique destinée à être utilisée par d’autres plugins SPIP (API exposées).


Installation

Le plugin s’installe comme tous les autres plugins SPIP. Pré-requis:

  • SPIP 4.x
  • PHP 8.x recommandé
  • Extension OpenSSL activée
  • cURL activé
  • Accès à un provider OAuth2

Principes

  • Agnostique : compatible avec tout provider OAuth2 / OIDC standard
  • Configuration dynamique à chaque appel
  • Architecture extensible (Providers / Grants / Pipelines)

Le plugin est un socle technique destiné à être utilisé par d’autres plugins SPIP.

Sécurité

Sécurité OAuth2 (RFC 6749)

  • Authorization Code Flow
  • Support Refresh Token sécurisé
  • Support Client Credentials
  • Nettoyage automatique des paramètres sensibles (code, state)
  • Suppression du contexte après usage
  • Gestion transactionnelle des erreurs

Sécurité PKCE (RFC 7636)

  • Génération cryptographiquement sécurisée du code_verifier
  • Support méthode S256
  • Stockage temporaire en session
  • Vérification automatique lors de l’échange
  • Nettoyage automatique après validation
  • Nettoyage des contextes expirés (> 10 minutes)

Sécurité OpenID Connect (Core 1.0)

Lorsque OIDC est activé :

  • Vérification signature JWT RS256
  • Récupération dynamique JWKS
  • Cache JWKS (1h)
  • Validation des claims :
    • iss
    • aud
    • exp (+ tolérance 60s)
    • nonce
    • at_hash (si présent)
  • Nettoyage automatique nonce + PKCE après validation

Validation cryptographique

  • Conversion JWK vers PEM sans dépendance externe
  • Vérification via OpenSSL PHP natif

Gestion du state

Le plugin utilise un stockage serveur du state afin de sécuriser et fiabiliser le flux OAuth2.

Chaque state est stocké temporairement (fichiers) avec :

  • session_id
  • user_agent
  • app
  • created_at

API principales

Obtenir un token: get_access_token()

oauth2_client_get_access_token(string $app, array $config, string $mode = 'session', ?int $user_id = null): string|false

$app est l’identifiant logique de la configuration OAuth2 utilisée (clé interne du plugin), permettant de distinguer plusieurs connexions/providers et de stocker séparément leurs tokens. Il peut être le même que celui déclaré auprès du provider (sans obligation): “monApp”, “SPIP”...

Modes supportés :

  • session : flux utilisateur (par défaut)
  • user : token persistant lié à un utilisateur SPIP
  • cron : token technique global

Cette API :

  • Retourne un access_token valide
  • Gère automatiquement :
    • expiration
    • refresh_token
    • échange authorization_code
    • PKCE
    • validation OIDC
    • nettoyage d’URL - gestion d’erreurs transactionnelles

Elle peut déclencher une redirection HTTP (mode session uniquement)

Configuration complète

Exemple de Keycloak avec un Provider générique en mode Authorization Code + PKCE + OIDC

include_spip('inc/oauth2_client');
$token = oauth2_client_get_access_token($app, [
	// Obligatoire : type de provider
	'provider'	=> 'generic',

	// Obligatoire : endpoints OAuth2
	'authorize_endpoint' =>	'http://localhost:8080/realms/demo/protocol/openid-connect/auth',
	'token_endpoint'	=> 'http://localhost:8080/realms/demo/protocol/openid-connect/token',

	// Obligatoire : identifiants client
	'client_id'	=> 'VOTRE_ID',
	'client_secret'	=> 'VOTRE_SECRET',

	// Obligatoire : URI de redirection enregistrée dans Keycloak
	'redirect_uri'	=> $redirect_uri,

	// Facultatif : Si PKCE (conseillé)
	'pkce' => [
		'enabled'	=> true,
		'method' 	=> 'S256',
	],

	// Facultatif : Obligatoire pour activer OIDC 
	'oidc' => [
		'enabled'	=> true,
	],

	// Facultatif : Obligatoire si OIDC activé
	'issuer'	=> 'http://localhost:8080/realms/demo',
	
	// Facultatif : Obligatoire selon contexte (openid...)
	'scope'	=> 'openid profile email',
]);

Configuration minimale

Exemple de Dropbox avec un Provider spécifique (exemple du Plugin Dropbox)

include_spip('inc/oauth2_client');
$token = oauth2_client_get_access_token($app, [
	'provider'   => 'dropbox',
	'client_id'   => $client_id ?? null,
	'client_secret' => $client_secret ?? null,
	'redirect_uri'  => $redirect_uri  ?? null,
 ]);

Obtenir un utilisateur: oauth2_client_get_user()

include_spip('inc/oauth2_client');
$user = oauth2_client_get_user($app, $mode = 'user', $user_id = null);

Retourne :

[
  'sub'         => '',
  'email'       => '',
  'username'    => '',
  'given_name'  => '',
  'family_name' => '',
  'name'        => '',
]

Extensibilité

Providers

Les providers encapsulent la configuration et les endpoints d’un serveur OAuth2 / OIDC. Deux types sont possibles :

  • Provider générique: Les endpoints (authorization, token, userinfo, etc.) sont fournis dynamiquement lors de l’appel.
  • Providers spécifiques: Dédiés à des services ou serveurs particuliers (paramètres, authentification client, particularités d’implémentation).

Ils n’ont pas vocation à être intégrés dans ce plugin cœur, mais peuvent être ajoutés pour étendre la classe générique.

Pour créer un provider spécifique, il suffit d’ajouter une classe héritant du Provider générique dans le dossier oauth_provider/MonProvider.php d’un plugin tiers (ou dans squelettes/). La classe Dropbox est fournie à simple titre d’exemple.

Grants

Les grants implémentent les différents flux OAuth2. Les flux actuellement pris en charge sont:

  • AuthorizationCode
  • RefreshToken
  • ClientCredentials

Chaque grant est implémenté sous forme de classe dédiée, ce qui permet d’en ajouter de nouveaux si nécessaire (par exemple Device Code, JWT Bearer, etc.).

Pour créer un grant spécifique, il suffit d’ajouter une classe héritant du Grant OAuth2ClientGrantAbstract dans le dossier oauth_grant/MonGrant.php d’un plugin tiers (ou dans squelettes/).

Pipelines

Le plugin OAuth2 Client propose plusieurs pipelines permettant aux plugins tiers de modifier dynamiquement les providers et les grants à différentes étapes du flux OAuth2.

oauth2_client_authorization_provider

Ce pipeline est appelé lors de la génération de l’URL d’autorisation OAuth2, après la création (instance) du provider.

$provider = pipeline('oauth2_client_authorization_provider',
	[
	'args' => [
		'app'  => $app,
		'config' => $config,
    ],
   'data' => $provider,
  ]);

oauth2_client_token_provider

Ce pipeline est appelé lors de la récupération ou du rafraîchissement d’un token, après la création (instance) du provider.

$provider = pipeline('oauth2_client_token_provider',
	[
	'args' => [
		'app'  => $app,
		'config' => $config,
    ],
   'data' => $provider,
  ]);

oauth2_client_grant

Ce pipeline est appelé lors de la récupération du Grant, après la création (instance) du grant.

$grant = pipeline('oauth2_client_grant',
	[
	'args' => [
		'app'    => $app,
		'grant_type' => $config['grant_type'],
		'config'   => $config,
		],
	'data' => $grant,
	]);

Stockage

Les tokens sont stockés dans la meta oauth2_client:

$meta[$storage_key] = [
  access_token,
  refresh_token,
  id_token,
  token_type,
  expires_at,
  user,
  stored_at
];

La clé de stockage dépend du contexte :

  • session : basé sur session_id
  • user : basé sur user_id
  • cron : global par application

Démonstration

En cours de réalisation. Actuellement une version “brut” est disponible sous demo/demo.html (accessible depuis l’administtration du plugin) sur la base d’un Docker/Keycloack en local

  • realms = ’demo’
  • client_id = VOTRE_ID
  • client_secret = VOTRE_SECRET

Version en cours de stabilisation.

Discussion

No discussion

Add a comment

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Who are you?
[Log in]

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.

Add a document

Follow the comments: RSS 2.0 | Atom