package service import ( "context" "encoding/json" "io" "net/http" "testing" "github.com/go-jose/go-jose/v4" josejwt "github.com/go-jose/go-jose/v4/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBackchannelLogoutService_BuildLogoutToken(t *testing.T) { t.Setenv("BACKCHANNEL_LOGOUT_ISSUER", "https://sso.example.com/oidc") svc, err := NewBackchannelLogoutService() require.NoError(t, err) token, err := svc.BuildLogoutToken("client-1", "user-1", "sid-1") require.NoError(t, err) require.NotEmpty(t, token) jwksRaw, err := svc.MarshalPublicJWKS() require.NoError(t, err) var jwks struct { Keys []jose.JSONWebKey `json:"keys"` } require.NoError(t, json.Unmarshal(jwksRaw, &jwks)) require.Len(t, jwks.Keys, 1) parsed, err := josejwt.ParseSigned(token, []jose.SignatureAlgorithm{jose.RS256}) require.NoError(t, err) var claims struct { Issuer string `json:"iss"` Subject string `json:"sub"` Aud any `json:"aud"` Iat int64 `json:"iat"` Jti string `json:"jti"` Sid string `json:"sid"` Events map[string]any `json:"events"` } require.NoError(t, parsed.Claims(jwks.Keys[0].Key, &claims)) assert.Equal(t, "https://sso.example.com/oidc", claims.Issuer) assert.Equal(t, "user-1", claims.Subject) switch aud := claims.Aud.(type) { case string: assert.Equal(t, "client-1", aud) case []any: assert.Len(t, aud, 1) assert.Equal(t, "client-1", aud[0]) default: t.Fatalf("unexpected aud type: %T", claims.Aud) } assert.NotZero(t, claims.Iat) assert.NotEmpty(t, claims.Jti) assert.Equal(t, "sid-1", claims.Sid) _, ok := claims.Events[backchannelLogoutEventURI] assert.True(t, ok) } func TestBackchannelLogoutService_SendLogoutToken(t *testing.T) { var body string handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) raw, _ := io.ReadAll(r.Body) body = string(raw) w.WriteHeader(http.StatusNoContent) }) svc, err := NewBackchannelLogoutService() require.NoError(t, err) svc.HTTPClient = clientForHandler(handler) statusCode, err := svc.SendLogoutToken(context.Background(), "https://rp.example.com/backchannel-logout", "signed-token") require.NoError(t, err) assert.Equal(t, http.StatusNoContent, statusCode) assert.Equal(t, "logout_token=signed-token", body) }