Mint and verify short-lived RS256 user tokens.
ExApps sign after AppAPI auth; Microservices check a static public key or an OIDC issuer and forward the same bearer. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package usertoken
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func TestOIDCVerifier(t *testing.T) {
|
||||
key := testKey(t)
|
||||
var srv *httptest.Server
|
||||
mux := http.NewServeMux()
|
||||
srv = httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"issuer": srv.URL,
|
||||
"jwks_uri": srv.URL + "/jwks",
|
||||
"id_token_signing_alg_values_supported": []string{"RS256"},
|
||||
})
|
||||
})
|
||||
mux.HandleFunc("/jwks", func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(publicJWKS(&key.PublicKey))
|
||||
})
|
||||
|
||||
auth, err := New(t.Context(), Config{
|
||||
OIDCIssuer: srv.URL,
|
||||
OIDCAudience: "mgmnt",
|
||||
GroupsClaim: "identity_groups",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
now := time.Now()
|
||||
groups := []string{"family"}
|
||||
raw, err := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
|
||||
"iss": srv.URL,
|
||||
"sub": "konrad",
|
||||
"preferred_username": "konrad",
|
||||
"aud": "mgmnt",
|
||||
"iat": now.Unix(),
|
||||
"exp": now.Add(time.Minute).Unix(),
|
||||
"identity_groups": groups,
|
||||
}).SignedString(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
caller, err := auth.Verify(t.Context(), raw)
|
||||
if err != nil {
|
||||
t.Fatalf("Verify: %v", err)
|
||||
}
|
||||
if caller.Subject != "konrad" || len(caller.Groups) != 1 || caller.Groups[0] != "family" {
|
||||
t.Fatalf("caller = %+v", caller)
|
||||
}
|
||||
|
||||
if _, err := New(t.Context(), Config{
|
||||
PublicKey: &key.PublicKey,
|
||||
Issuer: "manager",
|
||||
Audience: "mgmnt",
|
||||
OIDCIssuer: srv.URL,
|
||||
}); err == nil {
|
||||
t.Fatal("both modes accepted")
|
||||
}
|
||||
if _, err := auth.Verify(t.Context(), "not-a-token"); !errors.Is(err, ErrUnauthorized) {
|
||||
t.Fatalf("bad token err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func publicJWKS(pub *rsa.PublicKey) map[string]any {
|
||||
return map[string]any{
|
||||
"keys": []map[string]string{{
|
||||
"kty": "RSA",
|
||||
"use": "sig",
|
||||
"alg": "RS256",
|
||||
"n": base64.RawURLEncoding.EncodeToString(pub.N.Bytes()),
|
||||
"e": base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes()),
|
||||
}},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user