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
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