forked from baron/baron-sso
headless login SSA 백엔드 작업
This commit is contained in:
@@ -1648,17 +1648,6 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
|||||||
return errorJSON(c, fiber.StatusBadRequest, "type must be pkce or private")
|
return errorJSON(c, fiber.StatusBadRequest, "type must be pkce or private")
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Security] Check permission for private clients
|
|
||||||
if clientType == "private" {
|
|
||||||
isAppManager, err := h.checkAppManagerPermission(c)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
|
|
||||||
}
|
|
||||||
if !isAppManager && !h.canManageTenantClientsByPermit(c, profile, tenantID) {
|
|
||||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions to create private client")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status := strings.ToLower(strings.TrimSpace(valueOr(req.Status, "active")))
|
status := strings.ToLower(strings.TrimSpace(valueOr(req.Status, "active")))
|
||||||
if status != "active" && status != "inactive" {
|
if status != "active" && status != "inactive" {
|
||||||
return errorJSON(c, fiber.StatusBadRequest, "status must be active or inactive")
|
return errorJSON(c, fiber.StatusBadRequest, "status must be active or inactive")
|
||||||
@@ -1700,6 +1689,18 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
clientType = normalizeClientTypeForHeadless(clientType, metadata)
|
||||||
|
|
||||||
|
// [Security] Check permission for private clients
|
||||||
|
if clientType == "private" {
|
||||||
|
isAppManager, err := h.checkAppManagerPermission(c)
|
||||||
|
if err != nil {
|
||||||
|
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
|
||||||
|
}
|
||||||
|
if !isAppManager && !h.canManageTenantClientsByPermit(c, profile, tenantID) {
|
||||||
|
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions to create private client")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
|
tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
|
||||||
if tokenAuthMethod == "" {
|
if tokenAuthMethod == "" {
|
||||||
@@ -1709,11 +1710,10 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
|||||||
tokenAuthMethod = "client_secret_basic"
|
tokenAuthMethod = "client_secret_basic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := validateHeadlessClientInput(clientType, valueOr(req.JwksUri, ""), req.Jwks, metadata); err != nil {
|
if err := validateHeadlessClientInput(valueOr(req.JwksUri, ""), req.Jwks, metadata); err != nil {
|
||||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
tokenAuthMethod, jwksURI, jwks, metadata := normalizeHeadlessClientConfig(
|
tokenAuthMethod, jwksURI, jwks, metadata := normalizeHeadlessClientConfig(
|
||||||
clientType,
|
|
||||||
tokenAuthMethod,
|
tokenAuthMethod,
|
||||||
valueOr(req.JwksUri, ""),
|
valueOr(req.JwksUri, ""),
|
||||||
req.Jwks,
|
req.Jwks,
|
||||||
@@ -1900,21 +1900,21 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
|||||||
if clientType != "" {
|
if clientType != "" {
|
||||||
resolvedClientType = clientType
|
resolvedClientType = clientType
|
||||||
}
|
}
|
||||||
|
resolvedClientType = normalizeClientTypeForHeadless(resolvedClientType, metadata)
|
||||||
resolvedTokenAuthMethod := resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod)
|
resolvedTokenAuthMethod := resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod)
|
||||||
resolvedJWKSURI := valueOr(req.JwksUri, current.JWKSUri)
|
resolvedJWKSURI := valueOr(req.JwksUri, current.JWKSUri)
|
||||||
resolvedJWKS := req.Jwks
|
resolvedJWKS := req.Jwks
|
||||||
if req.Jwks == nil {
|
if req.Jwks == nil {
|
||||||
if resolvedClientType == "pkce" && readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled) {
|
if readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled) {
|
||||||
resolvedJWKS = nil
|
resolvedJWKS = nil
|
||||||
} else {
|
} else {
|
||||||
resolvedJWKS = current.JWKS
|
resolvedJWKS = current.JWKS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := validateHeadlessClientInput(resolvedClientType, resolvedJWKSURI, resolvedJWKS, metadata); err != nil {
|
if err := validateHeadlessClientInput(resolvedJWKSURI, resolvedJWKS, metadata); err != nil {
|
||||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
resolvedTokenAuthMethod, resolvedJWKSURI, resolvedJWKS, metadata = normalizeHeadlessClientConfig(
|
resolvedTokenAuthMethod, resolvedJWKSURI, resolvedJWKS, metadata = normalizeHeadlessClientConfig(
|
||||||
resolvedClientType,
|
|
||||||
resolvedTokenAuthMethod,
|
resolvedTokenAuthMethod,
|
||||||
resolvedJWKSURI,
|
resolvedJWKSURI,
|
||||||
resolvedJWKS,
|
resolvedJWKS,
|
||||||
@@ -2633,12 +2633,10 @@ func (h *DevHandler) mapClientSummary(client domain.HydraClient) clientSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientType := "private"
|
clientType := "private"
|
||||||
if strings.EqualFold(client.TokenEndpointAuthMethod, "none") {
|
if client.IsHeadlessLoginEnabled() {
|
||||||
|
clientType = "private"
|
||||||
|
} else if strings.EqualFold(client.TokenEndpointAuthMethod, "none") {
|
||||||
clientType = "pkce"
|
clientType = "pkce"
|
||||||
} else if strings.EqualFold(client.TokenEndpointAuthMethod, "private_key_jwt") && client.Metadata != nil {
|
|
||||||
if val, ok := client.Metadata["headless_login_enabled"].(bool); ok && val {
|
|
||||||
clientType = "pkce"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name := strings.TrimSpace(client.ClientName)
|
name := strings.TrimSpace(client.ClientName)
|
||||||
@@ -2786,7 +2784,6 @@ func normalizeClientAutoLoginMetadata(metadata map[string]interface{}) (map[stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeHeadlessClientConfig(
|
func normalizeHeadlessClientConfig(
|
||||||
clientType string,
|
|
||||||
tokenAuthMethod string,
|
tokenAuthMethod string,
|
||||||
jwksURI string,
|
jwksURI string,
|
||||||
jwks interface{},
|
jwks interface{},
|
||||||
@@ -2798,12 +2795,12 @@ func normalizeHeadlessClientConfig(
|
|||||||
delete(metadata, domain.MetadataRequestObjectSigningAlg)
|
delete(metadata, domain.MetadataRequestObjectSigningAlg)
|
||||||
|
|
||||||
headlessEnabled := readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled)
|
headlessEnabled := readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled)
|
||||||
if clientType == "pkce" && headlessEnabled {
|
if headlessEnabled {
|
||||||
headlessTokenAuthMethod := readMetadataStringValue(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
headlessTokenAuthMethod := readMetadataStringValue(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
||||||
if headlessTokenAuthMethod == "" && !strings.EqualFold(strings.TrimSpace(tokenAuthMethod), "none") {
|
if headlessTokenAuthMethod == "" && strings.EqualFold(strings.TrimSpace(tokenAuthMethod), "private_key_jwt") {
|
||||||
headlessTokenAuthMethod = strings.TrimSpace(tokenAuthMethod)
|
headlessTokenAuthMethod = strings.TrimSpace(tokenAuthMethod)
|
||||||
}
|
}
|
||||||
if headlessTokenAuthMethod == "" {
|
if headlessTokenAuthMethod == "" || strings.EqualFold(headlessTokenAuthMethod, "none") {
|
||||||
headlessTokenAuthMethod = "private_key_jwt"
|
headlessTokenAuthMethod = "private_key_jwt"
|
||||||
}
|
}
|
||||||
metadata[domain.MetadataHeadlessTokenEndpointAuthMethod] = headlessTokenAuthMethod
|
metadata[domain.MetadataHeadlessTokenEndpointAuthMethod] = headlessTokenAuthMethod
|
||||||
@@ -2820,7 +2817,7 @@ func normalizeHeadlessClientConfig(
|
|||||||
|
|
||||||
delete(metadata, domain.MetadataHeadlessJWKS)
|
delete(metadata, domain.MetadataHeadlessJWKS)
|
||||||
|
|
||||||
return "none", "", nil, metadata
|
return headlessTokenAuthMethod, headlessJWKSURI, nil, metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
delete(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
||||||
@@ -2829,8 +2826,8 @@ func normalizeHeadlessClientConfig(
|
|||||||
return tokenAuthMethod, jwksURI, jwks, metadata
|
return tokenAuthMethod, jwksURI, jwks, metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateHeadlessClientInput(clientType string, jwksURI string, jwks interface{}, metadata map[string]interface{}) error {
|
func validateHeadlessClientInput(jwksURI string, jwks interface{}, metadata map[string]interface{}) error {
|
||||||
if clientType != "pkce" || !readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled) {
|
if !readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2848,6 +2845,13 @@ func validateHeadlessClientInput(clientType string, jwksURI string, jwks interfa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeClientTypeForHeadless(clientType string, metadata map[string]interface{}) string {
|
||||||
|
if readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled) {
|
||||||
|
return "private"
|
||||||
|
}
|
||||||
|
return clientType
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeIDTokenClaimsMetadata(metadata map[string]interface{}) (map[string]interface{}, error) {
|
func normalizeIDTokenClaimsMetadata(metadata map[string]interface{}) (map[string]interface{}, error) {
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -1467,7 +1467,7 @@ func TestCreateClient_HeadlessLoginPayloadMapping(t *testing.T) {
|
|||||||
|
|
||||||
body, _ := json.Marshal(map[string]any{
|
body, _ := json.Marshal(map[string]any{
|
||||||
"name": "Headless Login App",
|
"name": "Headless Login App",
|
||||||
"type": "pkce",
|
"type": "private",
|
||||||
"redirectUris": []string{"https://rp.example.com/callback"},
|
"redirectUris": []string{"https://rp.example.com/callback"},
|
||||||
"scopes": []string{"openid", "profile"},
|
"scopes": []string{"openid", "profile"},
|
||||||
"tokenEndpointAuthMethod": "private_key_jwt",
|
"tokenEndpointAuthMethod": "private_key_jwt",
|
||||||
@@ -1482,7 +1482,8 @@ func TestCreateClient_HeadlessLoginPayloadMapping(t *testing.T) {
|
|||||||
|
|
||||||
resp, _ := app.Test(req, -1)
|
resp, _ := app.Test(req, -1)
|
||||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
assert.Equal(t, "none", captured.TokenEndpointAuthMethod)
|
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||||
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.JWKSUri)
|
||||||
assert.Nil(t, captured.JWKS)
|
assert.Nil(t, captured.JWKS)
|
||||||
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
||||||
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
||||||
@@ -1633,6 +1634,87 @@ func TestCreateClient_AllowsExplicitSkipConsentFalse(t *testing.T) {
|
|||||||
assert.False(t, *captured.SkipConsent)
|
assert.False(t, *captured.SkipConsent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateClient_LegacyPKCEHeadlessInputIsNormalizedToPrivate(t *testing.T) {
|
||||||
|
var captured domain.HydraClient
|
||||||
|
|
||||||
|
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
if r.Method == http.MethodPost && r.URL.Path == "/clients" {
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = json.Unmarshal(body, &captured)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
return httpJSONAny(r, http.StatusCreated, map[string]any{
|
||||||
|
"client_id": captured.ClientID,
|
||||||
|
"client_name": captured.ClientName,
|
||||||
|
"redirect_uris": captured.RedirectURIs,
|
||||||
|
"grant_types": captured.GrantTypes,
|
||||||
|
"response_types": captured.ResponseTypes,
|
||||||
|
"scope": captured.Scope,
|
||||||
|
"token_endpoint_auth_method": captured.TokenEndpointAuthMethod,
|
||||||
|
"jwks_uri": captured.JWKSUri,
|
||||||
|
"jwks": captured.JWKS,
|
||||||
|
"metadata": captured.Metadata,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
return httpJSONAny(r, http.StatusNotFound, nil), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := &DevHandler{
|
||||||
|
Hydra: &service.HydraAdminService{
|
||||||
|
AdminURL: "http://hydra.test",
|
||||||
|
PublicURL: "http://hydra.public",
|
||||||
|
HTTPClient: &http.Client{Transport: transport},
|
||||||
|
},
|
||||||
|
Keto: new(devMockKetoService),
|
||||||
|
}
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
app.Use(func(c *fiber.Ctx) error {
|
||||||
|
c.Locals("user_profile", &domain.UserProfileResponse{ID: "test-user", Role: domain.RoleSuperAdmin})
|
||||||
|
return c.Next()
|
||||||
|
})
|
||||||
|
app.Post("/api/v1/dev/clients", h.CreateClient)
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]any{
|
||||||
|
"name": "Legacy Headless Login App",
|
||||||
|
"type": "pkce",
|
||||||
|
"redirectUris": []string{"https://rp.example.com/callback"},
|
||||||
|
"metadata": map[string]any{
|
||||||
|
"headless_login_enabled": true,
|
||||||
|
"headless_jwks_uri": "https://rp.example.com/.well-known/jwks.json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, _ := app.Test(req, -1)
|
||||||
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
|
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||||
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.JWKSUri)
|
||||||
|
assert.Nil(t, captured.JWKS)
|
||||||
|
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
||||||
|
assert.True(t, captured.IsHeadlessLoginEnabled())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapClientSummary_ClassifiesHeadlessLoginAsPrivate(t *testing.T) {
|
||||||
|
h := &DevHandler{}
|
||||||
|
|
||||||
|
summary := h.mapClientSummary(domain.HydraClient{
|
||||||
|
ClientID: "client-headless-login",
|
||||||
|
ClientName: "Headless Login App",
|
||||||
|
TokenEndpointAuthMethod: "none",
|
||||||
|
Metadata: map[string]any{
|
||||||
|
"status": "active",
|
||||||
|
"headless_login_enabled": true,
|
||||||
|
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||||
|
"headless_jwks_uri": "https://rp.example.com/.well-known/jwks.json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "private", summary.Type)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateClient_HeadlessLoginRejectsInlineJWKS(t *testing.T) {
|
func TestCreateClient_HeadlessLoginRejectsInlineJWKS(t *testing.T) {
|
||||||
var hydraCalled bool
|
var hydraCalled bool
|
||||||
h := &DevHandler{
|
h := &DevHandler{
|
||||||
@@ -1895,7 +1977,7 @@ func TestUpdateClient_HeadlessLoginPayloadMapping(t *testing.T) {
|
|||||||
|
|
||||||
body, _ := json.Marshal(map[string]any{
|
body, _ := json.Marshal(map[string]any{
|
||||||
"name": "Headless Login After",
|
"name": "Headless Login After",
|
||||||
"type": "pkce",
|
"type": "private",
|
||||||
"tokenEndpointAuthMethod": "private_key_jwt",
|
"tokenEndpointAuthMethod": "private_key_jwt",
|
||||||
"jwksUri": "https://rp.example.com/.well-known/jwks.json",
|
"jwksUri": "https://rp.example.com/.well-known/jwks.json",
|
||||||
"metadata": map[string]any{
|
"metadata": map[string]any{
|
||||||
@@ -1908,8 +1990,8 @@ func TestUpdateClient_HeadlessLoginPayloadMapping(t *testing.T) {
|
|||||||
|
|
||||||
resp, _ := app.Test(req, -1)
|
resp, _ := app.Test(req, -1)
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
assert.Equal(t, "none", captured.TokenEndpointAuthMethod)
|
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||||
assert.Equal(t, "", captured.JWKSUri)
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.JWKSUri)
|
||||||
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
||||||
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
||||||
_, hasInlineJWKS := captured.Metadata["headless_jwks"]
|
_, hasInlineJWKS := captured.Metadata["headless_jwks"]
|
||||||
@@ -2071,7 +2153,8 @@ func TestUpdateClient_HeadlessLoginIgnoresExistingTopLevelJWKS(t *testing.T) {
|
|||||||
resp, _ := app.Test(req, -1)
|
resp, _ := app.Test(req, -1)
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
assert.Nil(t, captured.JWKS)
|
assert.Nil(t, captured.JWKS)
|
||||||
assert.Equal(t, "", captured.JWKSUri)
|
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||||
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.JWKSUri)
|
||||||
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
||||||
_, hasRequestObjectAlg := captured.Metadata["request_object_signing_alg"]
|
_, hasRequestObjectAlg := captured.Metadata["request_object_signing_alg"]
|
||||||
assert.False(t, hasRequestObjectAlg)
|
assert.False(t, hasRequestObjectAlg)
|
||||||
|
|||||||
@@ -981,10 +981,8 @@ function ClientGeneralPage() {
|
|||||||
.map((scope) => scope.name.trim())
|
.map((scope) => scope.name.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const persistedClientType = headlessLoginEnabled ? "pkce" : clientType;
|
const persistedClientType = headlessLoginEnabled ? "private" : clientType;
|
||||||
const effectiveTokenEndpointAuthMethod = headlessLoginEnabled
|
const effectiveTokenEndpointAuthMethod = tokenEndpointAuthMethod;
|
||||||
? "none"
|
|
||||||
: tokenEndpointAuthMethod;
|
|
||||||
|
|
||||||
const payload: ClientUpsertRequest = {
|
const payload: ClientUpsertRequest = {
|
||||||
name,
|
name,
|
||||||
@@ -992,7 +990,8 @@ function ClientGeneralPage() {
|
|||||||
scopes: scopeNames,
|
scopes: scopeNames,
|
||||||
tokenEndpointAuthMethod: effectiveTokenEndpointAuthMethod,
|
tokenEndpointAuthMethod: effectiveTokenEndpointAuthMethod,
|
||||||
jwksUri:
|
jwksUri:
|
||||||
effectiveTokenEndpointAuthMethod === "private_key_jwt" &&
|
(headlessLoginEnabled ||
|
||||||
|
effectiveTokenEndpointAuthMethod === "private_key_jwt") &&
|
||||||
trimmedJwksUri
|
trimmedJwksUri
|
||||||
? trimmedJwksUri
|
? trimmedJwksUri
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user