oauthsonas

oauthsonas est un petit fournisseur OpenID Connect en mémoire pour le développement local et les tests d'intégration. Il implémente un vrai flux navigateur Authorization Code avec S256 PKCE, des refresh tokens rotatifs, des JWT d'accès et d'identité signés RS256, la découverte, JWKS, userinfo, logout et des personas configurables.

  • Module : github.com/optimiweb/oauthsonas
  • Install : go install github.com/optimiweb/oauthsonas/cmd/oauthsonas@latest
  • Licence : MIT
  • Issuer par défaut : http://127.0.0.1:8181 (correspondance exacte — ne substituez pas localhost)

Avertissement de sécurité

Usage développement et test uniquement. N'exposez jamais ce serveur à Internet et ne le déployez pas en production.

Il dispose d'un sélecteur de personas en mémoire à la place d'une vraie authentification, génère une nouvelle clé de signature à chaque démarrage, et n'a pas d'état persistant. Il se lie à 127.0.0.1 par défaut et refuse les adresses non-loopback sauf si OAUTHSONAS_ALLOW_NON_LOOPBACK=true est défini explicitement.

Lisez la politique de sécurité avant de signaler une vulnérabilité.

À quoi ça sert

  • Remplacer un fournisseur OIDC externe en développement local et en CI sans court-circuiter le vrai flux navigateur de la partie cliente.
  • Exercer la découverte, Authorization Code + S256 PKCE, la préservation du state de callback, l'échange de tokens, la validation JWKS, userinfo, logout et la rotation des refresh tokens.
  • Fournir des claims applicatifs stables (roles, org_id optionnel, memberships optionnel) avec des noms de claims configurables.
  • Garder des identités reproductibles en YAML tout en laissant l'autorisation applicative et l'expansion rôle → permissions à la partie cliente.

Ce que ce n'est pas

Il n'y a intentionnellement pas de base de données, de flux mot de passe, d'API d'admin, d'UI de provisioning, d'API Organizations / Management Auth0, de fournisseur d'identité externe, ni de manifeste de déploiement production. Tout l'état protocole est en mémoire — redémarrez le processus pour effacer codes et tokens émis.

Démarrage rapide

  1. Démarrez le fournisseur avec le client et les personas d'exemple :

    go run ./cmd/oauthsonas --config config.example.yaml
    
  2. Pointez votre dashboard (ou autre RP) vers les variables de l'exemple dashboard.

  3. Lancez le flux de login et choisissez un persona sur la page d'autorisation locale (par exemple Acme Administrator).

  4. Le callback reçoit un code d'autorisation normal ; la bibliothèque OIDC l'échange et valide les tokens RS256 contre le JWKS publié.

Valider la configuration sans démarrer le serveur :

go run ./cmd/oauthsonas --config config.example.yaml --check-config

Afficher la version :

oauthsonas --version

Découverte :

http://127.0.0.1:8181/.well-known/openid-configuration

Exécuter dans un conteneur

podman build -t oauthsonas -f Containerfile .
podman run --rm -p 127.0.0.1:8181:8181 \
  -e OAUTHSONAS_ALLOW_NON_LOOPBACK=true oauthsonas

Docker utilise les mêmes commandes avec docker à la place de podman.

L'image se lie à 0.0.0.0 pour que le port-forwarding fonctionne. Vous devez définir OAUTHSONAS_ALLOW_NON_LOOPBACK=true pour reconnaître l'exposition. Gardez le port publié lié à la loopback (-p 127.0.0.1:8181:8181).

Images publiées (après un tag Git SemVer) :

podman pull ghcr.io/optimiweb/oauthsonas:1.2.3
podman run --rm -p 127.0.0.1:8181:8181 \
  -e OAUTHSONAS_ALLOW_NON_LOOPBACK=true \
  ghcr.io/optimiweb/oauthsonas:1.2.3

Pousser v1.2.3 publie 1.2.3, 1.2, 1 et latest. Les tags pre-release tels que v1.2.3-rc.1 publient uniquement leur version exacte et ne déplacent pas latest.

Exemple dashboard

Configurez un dashboard navigateur avec l'équivalent de sa bibliothèque OIDC :

OIDC_AUTHORITY=http://127.0.0.1:8181
OIDC_CLIENT_ID=dashboard
OIDC_REDIRECT_URI=http://127.0.0.1:5173/auth/callback
OIDC_POST_LOGOUT_REDIRECT_URI=http://127.0.0.1:5173/
OIDC_SCOPE="openid profile email"
OIDC_AUDIENCE=https://api.optimicdn.test

Le client d'exemple est public et exige S256 PKCE. La page d'autorisation laisse un développeur choisir un persona configuré, puis redirige vers le callback du dashboard avec un code d'autorisation normal et un state inchangé.

Pour clients, personas, noms de claims et TTL, voir Configuration.

Comportement protocole

  • Flux : Authorization Code avec S256 PKCE obligatoire, response_type=code et scope openid.
  • Scopes : openid, profile, email, et optionnellement offline_access.
  • Audience : optionnelle à l'authorize ; si fournie elle doit égaler exactement api_audience. Les access tokens utilisent toujours cette audience API.
  • Refresh : offline_access émet un refresh token. Fosite fait tourner les refresh tokens et détecte/rejette la réutilisation. Fournir scope au refresh ne peut que réduire la portée ; l'ensemble plus étroit devient le grant du token rotaté.
  • Codes : courte durée de vie et à usage unique. Le state est renvoyé inchangé. Les nonces sont copiés dans les ID tokens.
  • Userinfo : GET ou POST /userinfo valide le bearer access token et renvoie le sous-ensemble de claims profil/personnalisés accordé.
  • Logout : GET /logout ne redirige que vers un post_logout_redirect_uri enregistré. Avec id_token_hint, le client est dérivé de l'aud du token (logout initié par le RP sans client_id explicite).

Endpoints

MéthodeCheminRôle
GET/healthzLiveness
GET/readyzReadiness
GET/.well-known/openid-configurationDécouverte
GET/.well-known/jwks.jsonClés publiques de signature
GET/oauth2/authAutorisation (sélecteur de personas)
POST/oauth2/auth/selectSélection de persona
POST/oauth2/tokenEndpoint token
GET | POST/userinfoUserinfo
GET/logoutLogout initié par le RP

Santé et readiness

GET /healthz renvoie 200 :

{"status":"ok","version":"1.0.0","issuer":"http://127.0.0.1:8181","uptime":"5m23s"}

GET /readyz renvoie 200 une fois le serveur prêt :

{"status":"ready","issuer":"http://127.0.0.1:8181"}

Les deux définissent Cache-Control: no-store. Utilisez-les pour les probes Kubernetes ou le signalement d'orchestration.

Journalisation structurée

Le serveur utilise log/slog en JSON (défaut) ou texte :

--log-format json|text   (défaut : json)
--log-level  debug|info|warn|error   (défaut : info)

Attributs de requête : method, path, status, duration. Les événements OAuth ajoutent client_id, des champs grant/persona, et un interaction_id tronqué le cas échéant. Jamais journalisés : codes d'autorisation, access/refresh/ID tokens, cookies, valeurs CSRF ou vérificateurs PKCE.

Contraintes Kubernetes

oauthsonas est conçu pour des déploiements single-replica uniquement :

  • Une réplique max. Tokens, codes, interactions et clés de signature sont locaux au processus. Plusieurs répliques provoquent des décalages de cookies et un JWKS invalide.
  • Pas de rolling surge. Utilisez strategy: Recreate. Un second pod génèrerait une clé différente et casserait la validation.
  • Pas d'équilibrage de charge partagé entre instances.
  • Un redémarrage invalide tout — tous les tokens et codes émis sont perdus.
  • Utilisez /healthz pour la liveness et /readyz pour la readiness.

Le serveur garbage-collecte l'état expiré en mémoire. Pour les instances longues, envisagez un cycle de vie de processus qui redémarre périodiquement le pod.

Automation navigateur

Le sélecteur de personas expose des sélecteurs stables pour Playwright, Cypress et outils similaires.

ÉlémentSélecteur
Formulaire personaform[data-testid="persona-form-<id>"] ou .persona-form
Champ CSRFinput[data-testid="csrf-input"]
Interaction IDinput[data-testid="interaction-id-input"]
Soumissionbutton[data-testid="persona-select-<id>"]

Flux

  1. Naviguez vers GET /oauth2/auth?… avec les paramètres OIDC standard.
  2. La page contient un <form> par persona.
  3. Extrayez csrf et interaction_id des champs cachés.
  4. POST /oauth2/auth/select avec csrf, interaction_id et persona.
  5. Attendez un 302 vers le redirect_uri enregistré avec code et state.
  6. Échangez le code à POST /oauth2/token.

Exigences : inclure le cookie oauthsonas_interaction_<id> de l'étape 1 ; le CSRF est à usage unique (replay → 403) ; paramètres de formulaire en double → 400.

Tester et contribuer

go test -race ./...
go vet ./...

Voir CONTRIBUTING.md pour les guidelines de pull request.

Suite