authentik.providers.oauth2.views.provider

authentik OAuth2 OpenID well-known views

  1"""authentik OAuth2 OpenID well-known views"""
  2
  3from typing import Any
  4
  5from django.http import HttpRequest, HttpResponse, JsonResponse
  6from django.shortcuts import get_object_or_404, reverse
  7from django.views import View
  8from guardian.shortcuts import get_anonymous_user
  9from structlog.stdlib import get_logger
 10
 11from authentik.common.oauth.constants import (
 12    ACR_AUTHENTIK_DEFAULT,
 13    GRANT_TYPE_AUTHORIZATION_CODE,
 14    GRANT_TYPE_CLIENT_CREDENTIALS,
 15    GRANT_TYPE_DEVICE_CODE,
 16    GRANT_TYPE_IMPLICIT,
 17    GRANT_TYPE_PASSWORD,
 18    GRANT_TYPE_REFRESH_TOKEN,
 19    PKCE_METHOD_PLAIN,
 20    PKCE_METHOD_S256,
 21    SCOPE_OPENID,
 22)
 23from authentik.core.expression.exceptions import PropertyMappingExpressionException
 24from authentik.core.models import Application
 25from authentik.providers.oauth2.models import (
 26    OAuth2Provider,
 27    ResponseMode,
 28    ResponseTypes,
 29    ScopeMapping,
 30)
 31from authentik.providers.oauth2.utils import cors_allow
 32
 33LOGGER = get_logger()
 34
 35
 36class ProviderInfoView(View):
 37    """OpenID-compliant Provider Info"""
 38
 39    provider: OAuth2Provider
 40
 41    def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
 42        """Get dictionary for OpenID Connect information"""
 43        scopes = list(
 44            ScopeMapping.objects.filter(provider=provider).values_list("scope_name", flat=True)
 45        )
 46        if SCOPE_OPENID not in scopes:
 47            scopes.append(SCOPE_OPENID)
 48        _, supported_alg = provider.jwt_key
 49        config = {
 50            "issuer": provider.get_issuer(self.request),
 51            "authorization_endpoint": self.request.build_absolute_uri(
 52                reverse("authentik_providers_oauth2:authorize")
 53            ),
 54            "token_endpoint": self.request.build_absolute_uri(
 55                reverse("authentik_providers_oauth2:token")
 56            ),
 57            "userinfo_endpoint": self.request.build_absolute_uri(
 58                reverse("authentik_providers_oauth2:userinfo")
 59            ),
 60            "end_session_endpoint": self.request.build_absolute_uri(
 61                reverse(
 62                    "authentik_providers_oauth2:end-session",
 63                    kwargs={"application_slug": provider.application.slug},
 64                )
 65            ),
 66            "introspection_endpoint": self.request.build_absolute_uri(
 67                reverse("authentik_providers_oauth2:token-introspection")
 68            ),
 69            "revocation_endpoint": self.request.build_absolute_uri(
 70                reverse("authentik_providers_oauth2:token-revoke")
 71            ),
 72            "device_authorization_endpoint": self.request.build_absolute_uri(
 73                reverse("authentik_providers_oauth2:device")
 74            ),
 75            "backchannel_logout_supported": True,
 76            "backchannel_logout_session_supported": True,
 77            "response_types_supported": [
 78                ResponseTypes.CODE,
 79                ResponseTypes.ID_TOKEN,
 80                ResponseTypes.ID_TOKEN_TOKEN,
 81                ResponseTypes.CODE_TOKEN,
 82                ResponseTypes.CODE_ID_TOKEN,
 83                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 84            ],
 85            "response_modes_supported": [
 86                ResponseMode.QUERY,
 87                ResponseMode.FRAGMENT,
 88                ResponseMode.FORM_POST,
 89            ],
 90            "jwks_uri": self.request.build_absolute_uri(
 91                reverse(
 92                    "authentik_providers_oauth2:jwks",
 93                    kwargs={"application_slug": provider.application.slug},
 94                )
 95            ),
 96            "grant_types_supported": [
 97                GRANT_TYPE_AUTHORIZATION_CODE,
 98                GRANT_TYPE_REFRESH_TOKEN,
 99                GRANT_TYPE_IMPLICIT,
100                GRANT_TYPE_CLIENT_CREDENTIALS,
101                GRANT_TYPE_PASSWORD,
102                GRANT_TYPE_DEVICE_CODE,
103            ],
104            "id_token_signing_alg_values_supported": [supported_alg],
105            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
106            "subject_types_supported": ["public"],
107            "token_endpoint_auth_methods_supported": [
108                "client_secret_post",
109                "client_secret_basic",
110            ],
111            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
112            "scopes_supported": scopes,
113            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
114            "request_parameter_supported": False,
115            "claims_supported": self.get_claims(provider),
116            "claims_parameter_supported": False,
117            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
118        }
119        if provider.encryption_key:
120            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
121            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
122        return config
123
124    def get_claims(self, provider: OAuth2Provider) -> list[str]:
125        """Get a list of supported claims based on configured scope mappings"""
126        default_claims = [
127            "sub",
128            "iss",
129            "aud",
130            "exp",
131            "iat",
132            "auth_time",
133            "acr",
134            "amr",
135            "nonce",
136        ]
137        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
138            value = None
139            try:
140                value = scope.evaluate(
141                    user=get_anonymous_user(),
142                    request=self.request,
143                    provider=provider,
144                )
145            except PropertyMappingExpressionException:
146                continue
147            if value is None:
148                continue
149            if not isinstance(value, dict):
150                continue
151            default_claims.extend(value.keys())
152        return default_claims
153
154    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
155        """OpenID-compliant Provider Info"""
156        return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})
157
158    def dispatch(
159        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
160    ) -> HttpResponse:
161        # Since this view only supports get, we can statically set the CORS headers
162        application = get_object_or_404(Application, slug=application_slug)
163        self.provider: OAuth2Provider = get_object_or_404(
164            OAuth2Provider, pk=application.provider_id
165        )
166        response = super().dispatch(request, *args, **kwargs)
167        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
168        return response
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
class ProviderInfoView(django.views.generic.base.View):
 37class ProviderInfoView(View):
 38    """OpenID-compliant Provider Info"""
 39
 40    provider: OAuth2Provider
 41
 42    def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
 43        """Get dictionary for OpenID Connect information"""
 44        scopes = list(
 45            ScopeMapping.objects.filter(provider=provider).values_list("scope_name", flat=True)
 46        )
 47        if SCOPE_OPENID not in scopes:
 48            scopes.append(SCOPE_OPENID)
 49        _, supported_alg = provider.jwt_key
 50        config = {
 51            "issuer": provider.get_issuer(self.request),
 52            "authorization_endpoint": self.request.build_absolute_uri(
 53                reverse("authentik_providers_oauth2:authorize")
 54            ),
 55            "token_endpoint": self.request.build_absolute_uri(
 56                reverse("authentik_providers_oauth2:token")
 57            ),
 58            "userinfo_endpoint": self.request.build_absolute_uri(
 59                reverse("authentik_providers_oauth2:userinfo")
 60            ),
 61            "end_session_endpoint": self.request.build_absolute_uri(
 62                reverse(
 63                    "authentik_providers_oauth2:end-session",
 64                    kwargs={"application_slug": provider.application.slug},
 65                )
 66            ),
 67            "introspection_endpoint": self.request.build_absolute_uri(
 68                reverse("authentik_providers_oauth2:token-introspection")
 69            ),
 70            "revocation_endpoint": self.request.build_absolute_uri(
 71                reverse("authentik_providers_oauth2:token-revoke")
 72            ),
 73            "device_authorization_endpoint": self.request.build_absolute_uri(
 74                reverse("authentik_providers_oauth2:device")
 75            ),
 76            "backchannel_logout_supported": True,
 77            "backchannel_logout_session_supported": True,
 78            "response_types_supported": [
 79                ResponseTypes.CODE,
 80                ResponseTypes.ID_TOKEN,
 81                ResponseTypes.ID_TOKEN_TOKEN,
 82                ResponseTypes.CODE_TOKEN,
 83                ResponseTypes.CODE_ID_TOKEN,
 84                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 85            ],
 86            "response_modes_supported": [
 87                ResponseMode.QUERY,
 88                ResponseMode.FRAGMENT,
 89                ResponseMode.FORM_POST,
 90            ],
 91            "jwks_uri": self.request.build_absolute_uri(
 92                reverse(
 93                    "authentik_providers_oauth2:jwks",
 94                    kwargs={"application_slug": provider.application.slug},
 95                )
 96            ),
 97            "grant_types_supported": [
 98                GRANT_TYPE_AUTHORIZATION_CODE,
 99                GRANT_TYPE_REFRESH_TOKEN,
100                GRANT_TYPE_IMPLICIT,
101                GRANT_TYPE_CLIENT_CREDENTIALS,
102                GRANT_TYPE_PASSWORD,
103                GRANT_TYPE_DEVICE_CODE,
104            ],
105            "id_token_signing_alg_values_supported": [supported_alg],
106            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
107            "subject_types_supported": ["public"],
108            "token_endpoint_auth_methods_supported": [
109                "client_secret_post",
110                "client_secret_basic",
111            ],
112            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
113            "scopes_supported": scopes,
114            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
115            "request_parameter_supported": False,
116            "claims_supported": self.get_claims(provider),
117            "claims_parameter_supported": False,
118            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
119        }
120        if provider.encryption_key:
121            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
122            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
123        return config
124
125    def get_claims(self, provider: OAuth2Provider) -> list[str]:
126        """Get a list of supported claims based on configured scope mappings"""
127        default_claims = [
128            "sub",
129            "iss",
130            "aud",
131            "exp",
132            "iat",
133            "auth_time",
134            "acr",
135            "amr",
136            "nonce",
137        ]
138        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
139            value = None
140            try:
141                value = scope.evaluate(
142                    user=get_anonymous_user(),
143                    request=self.request,
144                    provider=provider,
145                )
146            except PropertyMappingExpressionException:
147                continue
148            if value is None:
149                continue
150            if not isinstance(value, dict):
151                continue
152            default_claims.extend(value.keys())
153        return default_claims
154
155    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
156        """OpenID-compliant Provider Info"""
157        return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})
158
159    def dispatch(
160        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
161    ) -> HttpResponse:
162        # Since this view only supports get, we can statically set the CORS headers
163        application = get_object_or_404(Application, slug=application_slug)
164        self.provider: OAuth2Provider = get_object_or_404(
165            OAuth2Provider, pk=application.provider_id
166        )
167        response = super().dispatch(request, *args, **kwargs)
168        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
169        return response

OpenID-compliant Provider Info

def get_info( self, provider: authentik.providers.oauth2.models.OAuth2Provider) -> dict[str, typing.Any]:
 42    def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
 43        """Get dictionary for OpenID Connect information"""
 44        scopes = list(
 45            ScopeMapping.objects.filter(provider=provider).values_list("scope_name", flat=True)
 46        )
 47        if SCOPE_OPENID not in scopes:
 48            scopes.append(SCOPE_OPENID)
 49        _, supported_alg = provider.jwt_key
 50        config = {
 51            "issuer": provider.get_issuer(self.request),
 52            "authorization_endpoint": self.request.build_absolute_uri(
 53                reverse("authentik_providers_oauth2:authorize")
 54            ),
 55            "token_endpoint": self.request.build_absolute_uri(
 56                reverse("authentik_providers_oauth2:token")
 57            ),
 58            "userinfo_endpoint": self.request.build_absolute_uri(
 59                reverse("authentik_providers_oauth2:userinfo")
 60            ),
 61            "end_session_endpoint": self.request.build_absolute_uri(
 62                reverse(
 63                    "authentik_providers_oauth2:end-session",
 64                    kwargs={"application_slug": provider.application.slug},
 65                )
 66            ),
 67            "introspection_endpoint": self.request.build_absolute_uri(
 68                reverse("authentik_providers_oauth2:token-introspection")
 69            ),
 70            "revocation_endpoint": self.request.build_absolute_uri(
 71                reverse("authentik_providers_oauth2:token-revoke")
 72            ),
 73            "device_authorization_endpoint": self.request.build_absolute_uri(
 74                reverse("authentik_providers_oauth2:device")
 75            ),
 76            "backchannel_logout_supported": True,
 77            "backchannel_logout_session_supported": True,
 78            "response_types_supported": [
 79                ResponseTypes.CODE,
 80                ResponseTypes.ID_TOKEN,
 81                ResponseTypes.ID_TOKEN_TOKEN,
 82                ResponseTypes.CODE_TOKEN,
 83                ResponseTypes.CODE_ID_TOKEN,
 84                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 85            ],
 86            "response_modes_supported": [
 87                ResponseMode.QUERY,
 88                ResponseMode.FRAGMENT,
 89                ResponseMode.FORM_POST,
 90            ],
 91            "jwks_uri": self.request.build_absolute_uri(
 92                reverse(
 93                    "authentik_providers_oauth2:jwks",
 94                    kwargs={"application_slug": provider.application.slug},
 95                )
 96            ),
 97            "grant_types_supported": [
 98                GRANT_TYPE_AUTHORIZATION_CODE,
 99                GRANT_TYPE_REFRESH_TOKEN,
100                GRANT_TYPE_IMPLICIT,
101                GRANT_TYPE_CLIENT_CREDENTIALS,
102                GRANT_TYPE_PASSWORD,
103                GRANT_TYPE_DEVICE_CODE,
104            ],
105            "id_token_signing_alg_values_supported": [supported_alg],
106            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
107            "subject_types_supported": ["public"],
108            "token_endpoint_auth_methods_supported": [
109                "client_secret_post",
110                "client_secret_basic",
111            ],
112            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
113            "scopes_supported": scopes,
114            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
115            "request_parameter_supported": False,
116            "claims_supported": self.get_claims(provider),
117            "claims_parameter_supported": False,
118            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
119        }
120        if provider.encryption_key:
121            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
122            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
123        return config

Get dictionary for OpenID Connect information

def get_claims( self, provider: authentik.providers.oauth2.models.OAuth2Provider) -> list[str]:
125    def get_claims(self, provider: OAuth2Provider) -> list[str]:
126        """Get a list of supported claims based on configured scope mappings"""
127        default_claims = [
128            "sub",
129            "iss",
130            "aud",
131            "exp",
132            "iat",
133            "auth_time",
134            "acr",
135            "amr",
136            "nonce",
137        ]
138        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
139            value = None
140            try:
141                value = scope.evaluate(
142                    user=get_anonymous_user(),
143                    request=self.request,
144                    provider=provider,
145                )
146            except PropertyMappingExpressionException:
147                continue
148            if value is None:
149                continue
150            if not isinstance(value, dict):
151                continue
152            default_claims.extend(value.keys())
153        return default_claims

Get a list of supported claims based on configured scope mappings

def get( self, request: django.http.request.HttpRequest, *args, **kwargs) -> django.http.response.HttpResponse:
155    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
156        """OpenID-compliant Provider Info"""
157        return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})

OpenID-compliant Provider Info

def dispatch( self, request: django.http.request.HttpRequest, application_slug: str, *args: Any, **kwargs: Any) -> django.http.response.HttpResponse:
159    def dispatch(
160        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
161    ) -> HttpResponse:
162        # Since this view only supports get, we can statically set the CORS headers
163        application = get_object_or_404(Application, slug=application_slug)
164        self.provider: OAuth2Provider = get_object_or_404(
165            OAuth2Provider, pk=application.provider_id
166        )
167        response = super().dispatch(request, *args, **kwargs)
168        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
169        return response