diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py
index ff272b5c62f2..67a829dd8808 100644
--- a/authentik/blueprints/v1/importer.py
+++ b/authentik/blueprints/v1/importer.py
@@ -65,7 +65,12 @@
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation
-from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
+from authentik.providers.oauth2.models import (
+ AccessToken,
+ AuthorizationCode,
+ DeviceToken,
+ RefreshToken,
+)
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.rbac.models import Role
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
@@ -125,6 +130,7 @@ def excluded_models() -> list[type[Model]]:
MicrosoftEntraProviderGroup,
EndpointDevice,
EndpointDeviceConnection,
+ DeviceToken,
)
diff --git a/authentik/providers/oauth2/api/providers.py b/authentik/providers/oauth2/api/providers.py
index 83d1cba2857a..daf87cac357b 100644
--- a/authentik/providers/oauth2/api/providers.py
+++ b/authentik/providers/oauth2/api/providers.py
@@ -73,7 +73,8 @@ class Meta:
"sub_mode",
"property_mappings",
"issuer_mode",
- "jwks_sources",
+ "jwt_federation_sources",
+ "jwt_federation_providers",
]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs
diff --git a/authentik/providers/oauth2/migrations/0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more.py b/authentik/providers/oauth2/migrations/0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more.py
new file mode 100644
index 000000000000..1f8292918924
--- /dev/null
+++ b/authentik/providers/oauth2/migrations/0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more.py
@@ -0,0 +1,25 @@
+# Generated by Django 5.0.9 on 2024-11-22 14:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("authentik_providers_oauth2", "0024_remove_oauth2provider_redirect_uris_and_more"),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="oauth2provider",
+ old_name="jwks_sources",
+ new_name="jwt_federation_sources",
+ ),
+ migrations.AddField(
+ model_name="oauth2provider",
+ name="jwt_federation_providers",
+ field=models.ManyToManyField(
+ blank=True, default=None, to="authentik_providers_oauth2.oauth2provider"
+ ),
+ ),
+ ]
diff --git a/authentik/providers/oauth2/models.py b/authentik/providers/oauth2/models.py
index 7e9ee3276f79..f6e5e7bb7333 100644
--- a/authentik/providers/oauth2/models.py
+++ b/authentik/providers/oauth2/models.py
@@ -244,7 +244,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
related_name="oauth2provider_encryption_key_set",
)
- jwks_sources = models.ManyToManyField(
+ jwt_federation_sources = models.ManyToManyField(
OAuthSource,
verbose_name=_(
"Any JWT signed by the JWK of the selected source can be used to authenticate."
@@ -253,6 +253,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
default=None,
blank=True,
)
+ jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None)
@cached_property
def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]:
diff --git a/authentik/providers/oauth2/tests/test_token_cc_jwt_provider.py b/authentik/providers/oauth2/tests/test_token_cc_jwt_provider.py
new file mode 100644
index 000000000000..abe1b5c7575f
--- /dev/null
+++ b/authentik/providers/oauth2/tests/test_token_cc_jwt_provider.py
@@ -0,0 +1,228 @@
+"""Test token view"""
+
+from datetime import datetime, timedelta
+from json import loads
+
+from django.test import RequestFactory
+from django.urls import reverse
+from django.utils.timezone import now
+from jwt import decode
+
+from authentik.blueprints.tests import apply_blueprint
+from authentik.core.models import Application, Group
+from authentik.core.tests.utils import create_test_cert, create_test_flow, create_test_user
+from authentik.lib.generators import generate_id
+from authentik.policies.models import PolicyBinding
+from authentik.providers.oauth2.constants import (
+ GRANT_TYPE_CLIENT_CREDENTIALS,
+ SCOPE_OPENID,
+ SCOPE_OPENID_EMAIL,
+ SCOPE_OPENID_PROFILE,
+ TOKEN_TYPE,
+)
+from authentik.providers.oauth2.models import (
+ AccessToken,
+ OAuth2Provider,
+ RedirectURI,
+ RedirectURIMatchingMode,
+ ScopeMapping,
+)
+from authentik.providers.oauth2.tests.utils import OAuthTestCase
+
+
+class TestTokenClientCredentialsJWTProvider(OAuthTestCase):
+ """Test token (client_credentials, with JWT) view"""
+
+ @apply_blueprint("system/providers-oauth2.yaml")
+ def setUp(self) -> None:
+ super().setUp()
+ self.factory = RequestFactory()
+ self.other_cert = create_test_cert()
+ self.cert = create_test_cert()
+
+ self.other_provider = OAuth2Provider.objects.create(
+ name=generate_id(),
+ authorization_flow=create_test_flow(),
+ signing_key=self.other_cert,
+ )
+ self.other_provider.property_mappings.set(ScopeMapping.objects.all())
+ self.app = Application.objects.create(
+ name=generate_id(), slug=generate_id(), provider=self.other_provider
+ )
+
+ self.provider: OAuth2Provider = OAuth2Provider.objects.create(
+ name="test",
+ authorization_flow=create_test_flow(),
+ redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
+ signing_key=self.cert,
+ )
+ self.provider.jwt_federation_providers.add(self.other_provider)
+ self.provider.property_mappings.set(ScopeMapping.objects.all())
+ self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
+
+ def test_invalid_type(self):
+ """test invalid type"""
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "foo",
+ "client_assertion": "foo.bar",
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_invalid_jwt(self):
+ """test invalid JWT"""
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": "foo.bar",
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_invalid_signature(self):
+ """test invalid JWT"""
+ token = self.provider.encode(
+ {
+ "sub": "foo",
+ "exp": datetime.now() + timedelta(hours=2),
+ }
+ )
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": token + "foo",
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_invalid_expired(self):
+ """test invalid JWT"""
+ token = self.provider.encode(
+ {
+ "sub": "foo",
+ "exp": datetime.now() - timedelta(hours=2),
+ }
+ )
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": token,
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_invalid_no_app(self):
+ """test invalid JWT"""
+ self.app.provider = None
+ self.app.save()
+ token = self.provider.encode(
+ {
+ "sub": "foo",
+ "exp": datetime.now() + timedelta(hours=2),
+ }
+ )
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": token,
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_invalid_access_denied(self):
+ """test invalid JWT"""
+ group = Group.objects.create(name="foo")
+ PolicyBinding.objects.create(
+ group=group,
+ target=self.app,
+ order=0,
+ )
+ token = self.provider.encode(
+ {
+ "sub": "foo",
+ "exp": datetime.now() + timedelta(hours=2),
+ }
+ )
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": token,
+ },
+ )
+ self.assertEqual(response.status_code, 400)
+ body = loads(response.content.decode())
+ self.assertEqual(body["error"], "invalid_grant")
+
+ def test_successful(self):
+ """test successful"""
+ user = create_test_user()
+ token = self.other_provider.encode(
+ {
+ "sub": "foo",
+ "exp": datetime.now() + timedelta(hours=2),
+ }
+ )
+ AccessToken.objects.create(
+ provider=self.other_provider,
+ token=token,
+ user=user,
+ auth_time=now(),
+ )
+
+ response = self.client.post(
+ reverse("authentik_providers_oauth2:token"),
+ {
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
+ "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
+ "client_id": self.provider.client_id,
+ "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ "client_assertion": token,
+ },
+ )
+ self.assertEqual(response.status_code, 200)
+ body = loads(response.content.decode())
+ self.assertEqual(body["token_type"], TOKEN_TYPE)
+ _, alg = self.provider.jwt_key
+ jwt = decode(
+ body["access_token"],
+ key=self.provider.signing_key.public_key,
+ algorithms=[alg],
+ audience=self.provider.client_id,
+ )
+ self.assertEqual(jwt["given_name"], user.name)
+ self.assertEqual(jwt["preferred_username"], user.username)
diff --git a/authentik/providers/oauth2/tests/test_token_cc_jwt_source.py b/authentik/providers/oauth2/tests/test_token_cc_jwt_source.py
index d52a2ed020de..5de0bd7ebc6c 100644
--- a/authentik/providers/oauth2/tests/test_token_cc_jwt_source.py
+++ b/authentik/providers/oauth2/tests/test_token_cc_jwt_source.py
@@ -37,9 +37,16 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
+ self.other_cert = create_test_cert()
+ # Provider used as a helper to sign JWTs with the same key as the OAuth source has
+ self.helper_provider = OAuth2Provider.objects.create(
+ name=generate_id(),
+ authorization_flow=create_test_flow(),
+ signing_key=self.other_cert,
+ )
self.cert = create_test_cert()
- jwk = JWKSView().get_jwk_for_key(self.cert, "sig")
+ jwk = JWKSView().get_jwk_for_key(self.other_cert, "sig")
self.source: OAuthSource = OAuthSource.objects.create(
name=generate_id(),
slug=generate_id(),
@@ -62,7 +69,7 @@ def setUp(self) -> None:
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
signing_key=self.cert,
)
- self.provider.jwks_sources.add(self.source)
+ self.provider.jwt_federation_sources.add(self.source)
self.provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
@@ -100,7 +107,7 @@ def test_invalid_jwt(self):
def test_invalid_signature(self):
"""test invalid JWT"""
- token = self.provider.encode(
+ token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@@ -122,7 +129,7 @@ def test_invalid_signature(self):
def test_invalid_expired(self):
"""test invalid JWT"""
- token = self.provider.encode(
+ token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() - timedelta(hours=2),
@@ -146,7 +153,7 @@ def test_invalid_no_app(self):
"""test invalid JWT"""
self.app.provider = None
self.app.save()
- token = self.provider.encode(
+ token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@@ -174,7 +181,7 @@ def test_invalid_access_denied(self):
target=self.app,
order=0,
)
- token = self.provider.encode(
+ token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@@ -196,7 +203,7 @@ def test_invalid_access_denied(self):
def test_successful(self):
"""test successful"""
- token = self.provider.encode(
+ token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
diff --git a/authentik/providers/oauth2/views/device_init.py b/authentik/providers/oauth2/views/device_init.py
index 85b32d805152..0fc114bcd418 100644
--- a/authentik/providers/oauth2/views/device_init.py
+++ b/authentik/providers/oauth2/views/device_init.py
@@ -137,7 +137,7 @@ def validate_code(self, code: int) -> HttpResponse | None:
class OAuthDeviceCodeStage(ChallengeStageView):
- """Flow challenge for users to enter device codes"""
+ """Flow challenge for users to enter device code"""
response_class = OAuthDeviceCodeChallengeResponse
diff --git a/authentik/providers/oauth2/views/token.py b/authentik/providers/oauth2/views/token.py
index 4ce13a6bcac4..9ee25dd555ed 100644
--- a/authentik/providers/oauth2/views/token.py
+++ b/authentik/providers/oauth2/views/token.py
@@ -362,23 +362,9 @@ def __post_init_client_credentials_creds(
},
).from_http(request, user=user)
- def __post_init_client_credentials_jwt(self, request: HttpRequest):
- assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
- if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
- LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
- raise TokenError("invalid_grant")
-
- client_secret = request.POST.get("client_secret", None)
- assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
- if not assertion:
- LOGGER.warning("Missing client assertion")
- raise TokenError("invalid_grant")
-
- token = None
-
- source: OAuthSource | None = None
- parsed_key: PyJWK | None = None
-
+ def __validate_jwt_from_source(
+ self, assertion: str
+ ) -> tuple[dict, OAuthSource] | tuple[None, None]:
# Fully decode the JWT without verifying the signature, so we can get access to
# the header.
# Get the Key ID from the header, and use that to optimise our source query to only find
@@ -394,7 +380,8 @@ def __post_init_client_credentials_jwt(self, request: HttpRequest):
raise TokenError("invalid_grant") from None
expected_kid = decode_unvalidated["header"]["kid"]
fallback_alg = decode_unvalidated["header"]["alg"]
- for source in self.provider.jwks_sources.filter(
+ token = source = None
+ for source in self.provider.jwt_federation_sources.filter(
oidc_jwks__keys__contains=[{"kid": expected_kid}]
):
LOGGER.debug("verifying JWT with source", source=source.slug)
@@ -404,10 +391,10 @@ def __post_init_client_credentials_jwt(self, request: HttpRequest):
continue
LOGGER.debug("verifying JWT with key", source=source.slug, key=key.get("kid"))
try:
- parsed_key = PyJWK.from_dict(key)
+ parsed_key = PyJWK.from_dict(key).key
token = decode(
assertion,
- parsed_key.key,
+ parsed_key,
algorithms=[key.get("alg")] if "alg" in key else [fallback_alg],
options={
"verify_aud": False,
@@ -417,13 +404,61 @@ def __post_init_client_credentials_jwt(self, request: HttpRequest):
# and not a public key
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, source=source.slug)
+ if token:
+ LOGGER.info("successfully verified JWT with source", source=source.slug)
+ return token, source
+
+ def __validate_jwt_from_provider(
+ self, assertion: str
+ ) -> tuple[dict, OAuth2Provider] | tuple[None, None]:
+ token = provider = _key = None
+ federated_token = AccessToken.objects.filter(
+ token=assertion, provider__in=self.provider.jwt_federation_providers.all()
+ ).first()
+ if federated_token:
+ _key, _alg = federated_token.provider.jwt_key
+ try:
+ token = decode(
+ assertion,
+ _key.public_key(),
+ algorithms=[_alg],
+ options={
+ "verify_aud": False,
+ },
+ )
+ provider = federated_token.provider
+ self.user = federated_token.user
+ except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
+ LOGGER.warning(
+ "failed to verify JWT", exc=exc, provider=federated_token.provider.name
+ )
+
+ if token:
+ LOGGER.info("successfully verified JWT with provider", provider=provider.name)
+ return token, provider
+
+ def __post_init_client_credentials_jwt(self, request: HttpRequest):
+ assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
+ if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
+ LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
+ raise TokenError("invalid_grant")
+
+ client_secret = request.POST.get("client_secret", None)
+ assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
+ if not assertion:
+ LOGGER.warning("Missing client assertion")
+ raise TokenError("invalid_grant")
+
+ source = provider = None
+
+ token, source = self.__validate_jwt_from_source(assertion)
+ if not token:
+ token, provider = self.__validate_jwt_from_provider(assertion)
if not token:
LOGGER.warning("No token could be verified")
raise TokenError("invalid_grant")
- LOGGER.info("successfully verified JWT with source", source=source.slug)
-
if "exp" in token:
exp = datetime.fromtimestamp(token["exp"])
# Non-timezone aware check since we assume `exp` is in UTC
@@ -437,15 +472,16 @@ def __post_init_client_credentials_jwt(self, request: HttpRequest):
raise TokenError("invalid_grant")
self.__check_policy_access(app, request, oauth_jwt=token)
- self.__create_user_from_jwt(token, app, source)
+ if not provider:
+ self.__create_user_from_jwt(token, app, source)
method_args = {
"jwt": token,
}
if source:
method_args["source"] = source
- if parsed_key:
- method_args["jwk_id"] = parsed_key.key_id
+ if provider:
+ method_args["provider"] = provider
Event.new(
action=EventAction.LOGIN,
**{
diff --git a/authentik/providers/proxy/api.py b/authentik/providers/proxy/api.py
index 2d02096bb87c..d228180ffe49 100644
--- a/authentik/providers/proxy/api.py
+++ b/authentik/providers/proxy/api.py
@@ -94,7 +94,8 @@ class Meta:
"intercept_header_auth",
"redirect_uris",
"cookie_domain",
- "jwks_sources",
+ "jwt_federation_sources",
+ "jwt_federation_providers",
"access_token_validity",
"refresh_token_validity",
"outpost_set",
diff --git a/blueprints/schema.json b/blueprints/schema.json
index 51e5dc871dd2..7ff0b11d1376 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -5617,13 +5617,20 @@
"title": "Issuer mode",
"description": "Configure how the issuer field of the ID Token should be filled."
},
- "jwks_sources": {
+ "jwt_federation_sources": {
"type": "array",
"items": {
"type": "integer",
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
+ },
+ "jwt_federation_providers": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "title": "Jwt federation providers"
}
},
"required": []
@@ -5746,7 +5753,7 @@
"type": "string",
"title": "Cookie domain"
},
- "jwks_sources": {
+ "jwt_federation_sources": {
"type": "array",
"items": {
"type": "integer",
@@ -5754,6 +5761,13 @@
},
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
+ "jwt_federation_providers": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "title": "Jwt federation providers"
+ },
"access_token_validity": {
"type": "string",
"minLength": 1,
diff --git a/schema.yml b/schema.yml
index ee2ad7970005..40d5ba2bd7a3 100644
--- a/schema.yml
+++ b/schema.yml
@@ -44785,7 +44785,7 @@ components:
allOf:
- $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled.
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -44793,6 +44793,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
required:
- assigned_application_name
- assigned_application_slug
@@ -44888,7 +44892,7 @@ components:
allOf:
- $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled.
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -44896,6 +44900,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
required:
- authorization_flow
- invalidation_flow
@@ -48911,7 +48919,7 @@ components:
allOf:
- $ref: '#/components/schemas/IssuerModeEnum'
description: Configure how the issuer field of the ID Token should be filled.
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -48919,6 +48927,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
PatchedOAuthSourcePropertyMappingRequest:
type: object
description: OAuthSourcePropertyMapping Serializer
@@ -49434,7 +49446,7 @@ components:
header and authenticate requests based on its value.
cookie_domain:
type: string
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -49442,6 +49454,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
access_token_validity:
type: string
minLength: 1
@@ -51504,7 +51520,7 @@ components:
readOnly: true
cookie_domain:
type: string
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -51512,6 +51528,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
access_token_validity:
type: string
description: 'Tokens not valid on or after current time + this value (Format:
@@ -51612,7 +51632,7 @@ components:
header and authenticate requests based on its value.
cookie_domain:
type: string
- jwks_sources:
+ jwt_federation_sources:
type: array
items:
type: string
@@ -51620,6 +51640,10 @@ components:
title: Any JWT signed by the JWK of the selected source can be used to
authenticate.
title: Any JWT signed by the JWK of the selected source can be used to authenticate.
+ jwt_federation_providers:
+ type: array
+ items:
+ type: integer
access_token_validity:
type: string
minLength: 1
diff --git a/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts
index 0eb85cf0d0ce..ebdf1472a336 100644
--- a/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts
+++ b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts
@@ -307,7 +307,7 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
>
${m}
`, )}`; +const providerToSelect = (provider: OAuth2Provider) => [provider.pk, provider.name]; + +export async function oauth2ProvidersProvider(page = 1, search = "") { + const oauthProviders = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2List({ + ordering: "name", + pageSize: 20, + search: search.trim(), + page, + }); + + return { + pagination: oauthProviders.pagination, + options: oauthProviders.results.map((provider) => providerToSelect(provider)), + }; +} + +export function oauth2ProviderSelector(instanceProviders: number[] | undefined) { + if (!instanceProviders) { + return async (mappings: DualSelectPair+ ${msg( + "JWTs signed by the selected providers can be used to authenticate to this provider.", + )} +
++ ${msg( + "JWTs signed by the selected providers can be used to authenticate to this provider.", + )} +
+