authentification OpenID Connect (Keycloak) : flux authorization code, state signé, validation du token ID via JWKS, provisioning automatique des utilisateurs

This commit is contained in:
fabien committed 2026-10-06 22:03:20 +02:00
1 parent 51bbb45b32
commit bedb6c98e7
15 files changed
+1057 -23

No files matched your search

@@ -0,0 +1,88 @@
package oidc
import (
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"time"
"trankilou.fr/lassistanoque/backend/internal/domain"
)
// providerMetadata est le document de découverte OpenID
// (.well-known/openid-configuration).
type providerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
JwksURI string `json:"jwks_uri"`
}
type metadataCacheEntry struct {
metadata *providerMetadata
expiresAt time.Time
}
// discovery maintient un cache des documents de découverte par fournisseur.
type discovery struct {
httpClient *http.Client
mu sync.Mutex
entries map[string]*metadataCacheEntry
}
func newDiscovery() *discovery {
return &discovery{
httpClient: &http.Client{Timeout: 10 * time.Second},
entries: make(map[string]*metadataCacheEntry),
}
}
// Metadata retourne les endpoints du fournisseur, avec mise en cache.
func (d *discovery) Metadata(provider *domain.OIDCProvider) (*providerMetadata, error) {
d.mu.Lock()
entry, ok := d.entries[provider.ID]
if ok && time.Now().Before(entry.expiresAt) {
d.mu.Unlock()
return entry.metadata, nil
}
d.mu.Unlock()
metadata, err := d.fetch(provider.WellknownURL)
if err != nil {
return nil, fmt.Errorf("error fetching provider discovery document: %s", err)
}
if metadata.Issuer == "" || metadata.AuthorizationEndpoint == "" || metadata.TokenEndpoint == "" || metadata.JwksURI == "" {
return nil, fmt.Errorf("incomplete discovery document for provider %s", provider.ID)
}
d.mu.Lock()
d.entries[provider.ID] = &metadataCacheEntry{
metadata: metadata,
expiresAt: time.Now().Add(1 * time.Hour),
}
d.mu.Unlock()
return metadata, nil
}
func (d *discovery) fetch(wellknownURL string) (*providerMetadata, error) {
res, err := d.httpClient.Get(wellknownURL)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status %d", res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
metadata := &providerMetadata{}
if err := json.Unmarshal(body, metadata); err != nil {
return nil, err
}
return metadata, nil
}