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