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            "frontchannel_logout_supported": True,
 78            "frontchannel_logout_session_supported": True,
 79            "response_types_supported": [
 80                ResponseTypes.CODE,
 81                ResponseTypes.ID_TOKEN,
 82                ResponseTypes.ID_TOKEN_TOKEN,
 83                ResponseTypes.CODE_TOKEN,
 84                ResponseTypes.CODE_ID_TOKEN,
 85                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 86            ],
 87            "response_modes_supported": [
 88                ResponseMode.QUERY,
 89                ResponseMode.FRAGMENT,
 90                ResponseMode.FORM_POST,
 91            ],
 92            "jwks_uri": self.request.build_absolute_uri(
 93                reverse(
 94                    "authentik_providers_oauth2:jwks",
 95                    kwargs={"application_slug": provider.application.slug},
 96                )
 97            ),
 98            "grant_types_supported": [
 99                GRANT_TYPE_AUTHORIZATION_CODE,
100                GRANT_TYPE_REFRESH_TOKEN,
101                GRANT_TYPE_IMPLICIT,
102                GRANT_TYPE_CLIENT_CREDENTIALS,
103                GRANT_TYPE_PASSWORD,
104                GRANT_TYPE_DEVICE_CODE,
105            ],
106            "id_token_signing_alg_values_supported": [supported_alg],
107            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
108            "subject_types_supported": ["public"],
109            "token_endpoint_auth_methods_supported": [
110                "client_secret_post",
111                "client_secret_basic",
112            ],
113            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
114            "scopes_supported": scopes,
115            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
116            "request_parameter_supported": False,
117            "claims_supported": self.get_claims(provider),
118            "claims_parameter_supported": False,
119            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
120        }
121        if provider.encryption_key:
122            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
123            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
124        return config
125
126    def get_claims(self, provider: OAuth2Provider) -> list[str]:
127        """Get a list of supported claims based on configured scope mappings"""
128        default_claims = [
129            "sub",
130            "iss",
131            "aud",
132            "exp",
133            "iat",
134            "auth_time",
135            "acr",
136            "amr",
137            "nonce",
138        ]
139        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
140            value = None
141            try:
142                value = scope.evaluate(
143                    user=get_anonymous_user(),
144                    request=self.request,
145                    provider=provider,
146                )
147            except PropertyMappingExpressionException:
148                continue
149            if value is None:
150                continue
151            if not isinstance(value, dict):
152                continue
153            default_claims.extend(value.keys())
154        return default_claims
155
156    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
157        """OpenID-compliant Provider Info"""
158        return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})
159
160    def dispatch(
161        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
162    ) -> HttpResponse:
163        # Since this view only supports get, we can statically set the CORS headers
164        application = get_object_or_404(Application, slug=application_slug)
165        self.provider: OAuth2Provider = get_object_or_404(
166            OAuth2Provider, pk=application.provider_id
167        )
168        response = super().dispatch(request, *args, **kwargs)
169        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
170        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            "frontchannel_logout_supported": True,
 79            "frontchannel_logout_session_supported": True,
 80            "response_types_supported": [
 81                ResponseTypes.CODE,
 82                ResponseTypes.ID_TOKEN,
 83                ResponseTypes.ID_TOKEN_TOKEN,
 84                ResponseTypes.CODE_TOKEN,
 85                ResponseTypes.CODE_ID_TOKEN,
 86                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 87            ],
 88            "response_modes_supported": [
 89                ResponseMode.QUERY,
 90                ResponseMode.FRAGMENT,
 91                ResponseMode.FORM_POST,
 92            ],
 93            "jwks_uri": self.request.build_absolute_uri(
 94                reverse(
 95                    "authentik_providers_oauth2:jwks",
 96                    kwargs={"application_slug": provider.application.slug},
 97                )
 98            ),
 99            "grant_types_supported": [
100                GRANT_TYPE_AUTHORIZATION_CODE,
101                GRANT_TYPE_REFRESH_TOKEN,
102                GRANT_TYPE_IMPLICIT,
103                GRANT_TYPE_CLIENT_CREDENTIALS,
104                GRANT_TYPE_PASSWORD,
105                GRANT_TYPE_DEVICE_CODE,
106            ],
107            "id_token_signing_alg_values_supported": [supported_alg],
108            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
109            "subject_types_supported": ["public"],
110            "token_endpoint_auth_methods_supported": [
111                "client_secret_post",
112                "client_secret_basic",
113            ],
114            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
115            "scopes_supported": scopes,
116            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
117            "request_parameter_supported": False,
118            "claims_supported": self.get_claims(provider),
119            "claims_parameter_supported": False,
120            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
121        }
122        if provider.encryption_key:
123            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
124            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
125        return config
126
127    def get_claims(self, provider: OAuth2Provider) -> list[str]:
128        """Get a list of supported claims based on configured scope mappings"""
129        default_claims = [
130            "sub",
131            "iss",
132            "aud",
133            "exp",
134            "iat",
135            "auth_time",
136            "acr",
137            "amr",
138            "nonce",
139        ]
140        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
141            value = None
142            try:
143                value = scope.evaluate(
144                    user=get_anonymous_user(),
145                    request=self.request,
146                    provider=provider,
147                )
148            except PropertyMappingExpressionException:
149                continue
150            if value is None:
151                continue
152            if not isinstance(value, dict):
153                continue
154            default_claims.extend(value.keys())
155        return default_claims
156
157    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
158        """OpenID-compliant Provider Info"""
159        return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})
160
161    def dispatch(
162        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
163    ) -> HttpResponse:
164        # Since this view only supports get, we can statically set the CORS headers
165        application = get_object_or_404(Application, slug=application_slug)
166        self.provider: OAuth2Provider = get_object_or_404(
167            OAuth2Provider, pk=application.provider_id
168        )
169        response = super().dispatch(request, *args, **kwargs)
170        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
171        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            "frontchannel_logout_supported": True,
 79            "frontchannel_logout_session_supported": True,
 80            "response_types_supported": [
 81                ResponseTypes.CODE,
 82                ResponseTypes.ID_TOKEN,
 83                ResponseTypes.ID_TOKEN_TOKEN,
 84                ResponseTypes.CODE_TOKEN,
 85                ResponseTypes.CODE_ID_TOKEN,
 86                ResponseTypes.CODE_ID_TOKEN_TOKEN,
 87            ],
 88            "response_modes_supported": [
 89                ResponseMode.QUERY,
 90                ResponseMode.FRAGMENT,
 91                ResponseMode.FORM_POST,
 92            ],
 93            "jwks_uri": self.request.build_absolute_uri(
 94                reverse(
 95                    "authentik_providers_oauth2:jwks",
 96                    kwargs={"application_slug": provider.application.slug},
 97                )
 98            ),
 99            "grant_types_supported": [
100                GRANT_TYPE_AUTHORIZATION_CODE,
101                GRANT_TYPE_REFRESH_TOKEN,
102                GRANT_TYPE_IMPLICIT,
103                GRANT_TYPE_CLIENT_CREDENTIALS,
104                GRANT_TYPE_PASSWORD,
105                GRANT_TYPE_DEVICE_CODE,
106            ],
107            "id_token_signing_alg_values_supported": [supported_alg],
108            # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
109            "subject_types_supported": ["public"],
110            "token_endpoint_auth_methods_supported": [
111                "client_secret_post",
112                "client_secret_basic",
113            ],
114            "acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
115            "scopes_supported": scopes,
116            # https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
117            "request_parameter_supported": False,
118            "claims_supported": self.get_claims(provider),
119            "claims_parameter_supported": False,
120            "code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
121        }
122        if provider.encryption_key:
123            config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
124            config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
125        return config

Get dictionary for OpenID Connect information

def get_claims( self, provider: authentik.providers.oauth2.models.OAuth2Provider) -> list[str]:
127    def get_claims(self, provider: OAuth2Provider) -> list[str]:
128        """Get a list of supported claims based on configured scope mappings"""
129        default_claims = [
130            "sub",
131            "iss",
132            "aud",
133            "exp",
134            "iat",
135            "auth_time",
136            "acr",
137            "amr",
138            "nonce",
139        ]
140        for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
141            value = None
142            try:
143                value = scope.evaluate(
144                    user=get_anonymous_user(),
145                    request=self.request,
146                    provider=provider,
147                )
148            except PropertyMappingExpressionException:
149                continue
150            if value is None:
151                continue
152            if not isinstance(value, dict):
153                continue
154            default_claims.extend(value.keys())
155        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:
157    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
158        """OpenID-compliant Provider Info"""
159        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:
161    def dispatch(
162        self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
163    ) -> HttpResponse:
164        # Since this view only supports get, we can statically set the CORS headers
165        application = get_object_or_404(Application, slug=application_slug)
166        self.provider: OAuth2Provider = get_object_or_404(
167            OAuth2Provider, pk=application.provider_id
168        )
169        response = super().dispatch(request, *args, **kwargs)
170        cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
171        return response