authentik.providers.oauth2.models
OAuth Provider Models
1"""OAuth Provider Models""" 2 3import base64 4import binascii 5import json 6from dataclasses import asdict, dataclass 7from functools import cached_property 8from hashlib import sha256 9from typing import TYPE_CHECKING, Any 10from urllib.parse import urlparse, urlunparse 11 12from cryptography.hazmat.primitives.asymmetric.ec import ( 13 SECP256R1, 14 SECP384R1, 15 SECP521R1, 16 EllipticCurvePrivateKey, 17) 18from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey 19from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes 20from dacite import Config 21from dacite.core import from_dict 22from django.contrib.postgres.fields import ArrayField 23from django.contrib.postgres.indexes import HashIndex 24from django.db import models 25from django.http import HttpRequest 26from django.templatetags.static import static 27from django.urls import reverse 28from django.utils.translation import gettext_lazy as _ 29from jwcrypto.common import json_encode 30from jwcrypto.jwe import JWE 31from jwcrypto.jwk import JWK 32from jwt import encode 33from rest_framework.serializers import Serializer 34from structlog.stdlib import get_logger 35 36from authentik.brands.models import WebfingerProvider 37from authentik.common.oauth.constants import ( 38 GRANT_TYPE_AUTHORIZATION_CODE, 39 GRANT_TYPE_CLIENT_CREDENTIALS, 40 GRANT_TYPE_DEVICE_CODE, 41 GRANT_TYPE_HYBRID, 42 GRANT_TYPE_IMPLICIT, 43 GRANT_TYPE_PASSWORD, 44 GRANT_TYPE_REFRESH_TOKEN, 45 SubModes, 46) 47from authentik.core.models import ( 48 AuthenticatedSession, 49 ExpiringModel, 50 PropertyMapping, 51 Provider, 52 User, 53) 54from authentik.crypto.models import CertificateKeyPair 55from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key 56from authentik.lib.models import DomainlessURLValidator, InternallyManagedMixin, SerializerModel 57from authentik.lib.utils.time import timedelta_string_validator 58from authentik.sources.oauth.models import OAuthSource 59 60if TYPE_CHECKING: 61 from authentik.providers.oauth2.id_token import IDToken 62 63LOGGER = get_logger() 64 65 66def generate_client_secret() -> str: 67 """Generate client secret with adequate length""" 68 return generate_id(128) 69 70 71class ClientType(models.TextChoices): 72 """Confidential clients are capable of maintaining the confidentiality 73 of their credentials. Public clients are incapable.""" 74 75 CONFIDENTIAL = "confidential", _("Confidential") 76 PUBLIC = "public", _("Public") 77 78 79class GrantType(models.TextChoices): 80 """OAuth2 Grant types we support""" 81 82 AUTHORIZATION_CODE = GRANT_TYPE_AUTHORIZATION_CODE 83 IMPLICIT = GRANT_TYPE_IMPLICIT 84 HYBRID = GRANT_TYPE_HYBRID 85 REFRESH_TOKEN = GRANT_TYPE_REFRESH_TOKEN 86 CLIENT_CREDENTIALS = GRANT_TYPE_CLIENT_CREDENTIALS 87 PASSWORD = GRANT_TYPE_PASSWORD 88 DEVICE_CODE = GRANT_TYPE_DEVICE_CODE 89 90 91# Fallback for decoding previous sessions from 2026.2 to 2026.5 92# https://github.com/goauthentik/authentik/issues/22588 93# TODO: Remove after 2026.8 94GrantTypes = GrantType 95 96 97class ResponseMode(models.TextChoices): 98 """https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#OAuth.Post""" 99 100 QUERY = "query" 101 FRAGMENT = "fragment" 102 FORM_POST = "form_post" 103 104 105class IssuerMode(models.TextChoices): 106 """Configure how the `iss` field is created.""" 107 108 GLOBAL = "global", _("Same identifier is used for all providers") 109 PER_PROVIDER = ( 110 "per_provider", 111 _("Each provider has a different issuer, based on the application slug."), 112 ) 113 114 115class RedirectURIMatchingMode(models.TextChoices): 116 STRICT = "strict", _("Strict URL comparison") 117 REGEX = "regex", _("Regular Expression URL matching") 118 119 120class RedirectURIType(models.TextChoices): 121 AUTHORIZATION = "authorization", _("Authorization") 122 LOGOUT = "logout", _("Logout") 123 124 125class OAuth2LogoutMethod(models.TextChoices): 126 """OAuth2/OIDC Logout methods""" 127 128 BACKCHANNEL = "backchannel", _("Back-channel") 129 FRONTCHANNEL = "frontchannel", _("Front-channel") 130 131 132@dataclass 133class RedirectURI: 134 """A single redirect URI entry""" 135 136 matching_mode: RedirectURIMatchingMode 137 url: str 138 redirect_uri_type: RedirectURIType = RedirectURIType.AUTHORIZATION 139 140 141class ResponseTypes(models.TextChoices): 142 """Response Type required by the client.""" 143 144 CODE = "code", _("code (Authorization Code Flow)") 145 ID_TOKEN = "id_token", _("id_token (Implicit Flow)") 146 ID_TOKEN_TOKEN = "id_token token", _("id_token token (Implicit Flow)") 147 CODE_TOKEN = "code token", _("code token (Hybrid Flow)") 148 CODE_ID_TOKEN = "code id_token", _("code id_token (Hybrid Flow)") 149 CODE_ID_TOKEN_TOKEN = "code id_token token", _("code id_token token (Hybrid Flow)") 150 151 152class JWTAlgorithms(models.TextChoices): 153 """Algorithm used to sign the JWT Token""" 154 155 HS256 = "HS256", _("HS256 (Symmetric Encryption)") 156 RS256 = "RS256", _("RS256 (Asymmetric Encryption)") 157 ES256 = "ES256", _("ES256 (Asymmetric Encryption)") 158 ES384 = "ES384", _("ES384 (Asymmetric Encryption)") 159 ES512 = "ES512", _("ES512 (Asymmetric Encryption)") 160 161 @classmethod 162 def from_private_key(cls, private_key: PrivateKeyTypes | None) -> str: 163 if isinstance(private_key, RSAPrivateKey): 164 return cls.RS256 165 if isinstance(private_key, EllipticCurvePrivateKey): 166 curve = private_key.curve 167 if isinstance(curve, SECP256R1): 168 return cls.ES256 169 if isinstance(curve, SECP384R1): 170 return cls.ES384 171 if isinstance(curve, SECP521R1): 172 return cls.ES512 173 raise ValueError(f"Invalid private key type: {type(private_key)}") 174 175 176class ScopeMapping(PropertyMapping): 177 """Map an OAuth Scope to users properties""" 178 179 scope_name = models.TextField(help_text=_("Scope used by the client")) 180 description = models.TextField( 181 blank=True, 182 help_text=_( 183 "Description shown to the user when consenting. " 184 "If left empty, the user won't be informed." 185 ), 186 ) 187 188 @property 189 def component(self) -> str: 190 return "ak-property-mapping-provider-scope-form" 191 192 @property 193 def serializer(self) -> type[Serializer]: 194 from authentik.providers.oauth2.api.scopes import ScopeMappingSerializer 195 196 return ScopeMappingSerializer 197 198 def __str__(self): 199 return f"Scope Mapping {self.name} ({self.scope_name})" 200 201 class Meta: 202 verbose_name = _("Scope Mapping") 203 verbose_name_plural = _("Scope Mappings") 204 205 206class OAuth2Provider(WebfingerProvider, Provider): 207 """OAuth2 Provider for generic OAuth and OpenID Connect Applications.""" 208 209 client_type = models.CharField( 210 max_length=30, 211 choices=ClientType.choices, 212 default=ClientType.CONFIDENTIAL, 213 verbose_name=_("Client Type"), 214 help_text=_( 215 "Confidential clients are capable of maintaining the confidentiality " 216 "of their credentials. Public clients are incapable" 217 ), 218 ) 219 grant_types = ArrayField(models.TextField(choices=GrantType.choices), default=list) 220 client_id = models.CharField( 221 max_length=255, 222 unique=True, 223 verbose_name=_("Client ID"), 224 default=generate_id, 225 ) 226 client_secret = models.CharField( 227 max_length=255, 228 blank=True, 229 verbose_name=_("Client Secret"), 230 default=generate_client_secret, 231 ) 232 _redirect_uris = models.JSONField( 233 default=list, 234 verbose_name=_("Redirect URIs"), 235 ) 236 logout_uri = models.TextField( 237 validators=[DomainlessURLValidator(schemes=("http", "https"))], 238 verbose_name=_("Logout URI"), 239 blank=True, 240 ) 241 logout_method = models.TextField( 242 choices=OAuth2LogoutMethod.choices, 243 default=OAuth2LogoutMethod.BACKCHANNEL, 244 verbose_name=_("Logout Method"), 245 help_text=_( 246 "Backchannel logs out with server to server calls. " 247 "Frontchannel uses iframes in your browser" 248 ), 249 ) 250 include_claims_in_id_token = models.BooleanField( 251 default=True, 252 verbose_name=_("Include claims in id_token"), 253 help_text=_( 254 "Include User claims from scopes in the id_token, for applications " 255 "that don't access the userinfo endpoint." 256 ), 257 ) 258 259 access_code_validity = models.TextField( 260 default="minutes=1", 261 validators=[timedelta_string_validator], 262 help_text=_( 263 "Access codes not valid on or after current time + this value " 264 "(Format: hours=1;minutes=2;seconds=3)." 265 ), 266 ) 267 access_token_validity = models.TextField( 268 default="hours=1", 269 validators=[timedelta_string_validator], 270 help_text=_( 271 "Tokens not valid on or after current time + this value " 272 "(Format: hours=1;minutes=2;seconds=3)." 273 ), 274 ) 275 refresh_token_validity = models.TextField( 276 default="days=30", 277 validators=[timedelta_string_validator], 278 help_text=_( 279 "Tokens not valid on or after current time + this value " 280 "(Format: hours=1;minutes=2;seconds=3)." 281 ), 282 ) 283 refresh_token_threshold = models.TextField( 284 default="seconds=0", 285 validators=[timedelta_string_validator], 286 help_text=_( 287 "When refreshing a token, if the refresh token is valid for less than " 288 "this duration, it will be renewed. " 289 "When set to seconds=0, token will always be renewed. " 290 "(Format: hours=1;minutes=2;seconds=3)." 291 ), 292 ) 293 294 sub_mode = models.TextField( 295 choices=SubModes.choices, 296 default=SubModes.HASHED_USER_ID, 297 help_text=_( 298 "Configure what data should be used as unique User Identifier. For most cases, " 299 "the default should be fine." 300 ), 301 ) 302 issuer_mode = models.TextField( 303 choices=IssuerMode.choices, 304 default=IssuerMode.PER_PROVIDER, 305 help_text=_("Configure how the issuer field of the ID Token should be filled."), 306 ) 307 308 signing_key = models.ForeignKey( 309 CertificateKeyPair, 310 verbose_name=_("Signing Key"), 311 on_delete=models.SET_NULL, 312 null=True, 313 help_text=_("Key used to sign the tokens."), 314 related_name="oauth2provider_signing_key_set", 315 ) 316 encryption_key = models.ForeignKey( 317 CertificateKeyPair, 318 verbose_name=_("Encryption Key"), 319 on_delete=models.SET_NULL, 320 null=True, 321 help_text=_( 322 "Key used to encrypt the tokens. When set, " 323 "tokens will be encrypted and returned as JWEs." 324 ), 325 related_name="oauth2provider_encryption_key_set", 326 ) 327 328 jwt_federation_sources = models.ManyToManyField( 329 OAuthSource, 330 verbose_name=_( 331 "Any JWT signed by the JWK of the selected source can be used to authenticate." 332 ), 333 related_name="oauth2_providers", 334 default=None, 335 blank=True, 336 ) 337 jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None) 338 339 @cached_property 340 def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]: 341 """Get either the configured certificate or the client secret""" 342 if not self.signing_key: 343 # No Certificate at all, assume HS256 344 return self.client_secret, JWTAlgorithms.HS256 345 key: CertificateKeyPair = self.signing_key 346 private_key = key.private_key 347 return private_key, JWTAlgorithms.from_private_key(private_key) 348 349 def get_issuer(self, request: HttpRequest) -> str | None: 350 """Get issuer, based on request""" 351 if self.issuer_mode == IssuerMode.GLOBAL: 352 return request.build_absolute_uri(reverse("authentik_core:root-redirect")) 353 try: 354 url = reverse( 355 "authentik_providers_oauth2:provider-root", 356 kwargs={ 357 "application_slug": self.application.slug, 358 }, 359 ) 360 return request.build_absolute_uri(url) 361 except Provider.application.RelatedObjectDoesNotExist: 362 return None 363 364 @property 365 def redirect_uris(self) -> list[RedirectURI]: 366 uris = [] 367 for entry in self._redirect_uris: 368 uris.append( 369 from_dict( 370 RedirectURI, 371 entry, 372 config=Config( 373 type_hooks={ 374 RedirectURIMatchingMode: RedirectURIMatchingMode, 375 RedirectURIType: RedirectURIType, 376 } 377 ), 378 ) 379 ) 380 return uris 381 382 @redirect_uris.setter 383 def redirect_uris(self, value: list[RedirectURI]): 384 cleansed = [] 385 for entry in value: 386 cleansed.append(asdict(entry)) 387 self._redirect_uris = cleansed 388 389 @property 390 def authorization_redirect_uris(self) -> list[RedirectURI]: 391 return [ 392 uri 393 for uri in self.redirect_uris 394 if uri.redirect_uri_type == RedirectURIType.AUTHORIZATION 395 ] 396 397 @property 398 def post_logout_redirect_uris(self) -> list[RedirectURI]: 399 return [ 400 uri for uri in self.redirect_uris if uri.redirect_uri_type == RedirectURIType.LOGOUT 401 ] 402 403 @property 404 def launch_url(self) -> str | None: 405 """Guess launch_url based on first redirect_uri""" 406 redirects = self.authorization_redirect_uris 407 if len(redirects) < 1: 408 return None 409 main_url = redirects[0].url 410 try: 411 launch_url = urlparse(main_url)._replace(path="") 412 return urlunparse(launch_url) 413 except ValueError as exc: 414 LOGGER.warning("Failed to format launch url", exc=exc) 415 return None 416 417 @property 418 def icon_url(self) -> str | None: 419 return static("authentik/sources/openidconnect.svg") 420 421 @property 422 def component(self) -> str: 423 return "ak-provider-oauth2-form" 424 425 @property 426 def serializer(self) -> type[Serializer]: 427 from authentik.providers.oauth2.api.providers import OAuth2ProviderSerializer 428 429 return OAuth2ProviderSerializer 430 431 def __str__(self): 432 return f"OAuth2 Provider {self.name}" 433 434 def encode(self, payload: dict[str, Any], jwt_type: str | None = None) -> str: 435 """Represent the ID Token as a JSON Web Token (JWT). 436 437 :param payload The payload to encode into the JWT 438 :param jwt_type The type of the JWT. This will be put in the JWT header using the `typ` 439 parameter. See RFC7515 Section 4.1.9. If not set fallback to the default of `JWT`. 440 """ 441 headers = {} 442 if self.signing_key: 443 headers["kid"] = self.signing_key.kid 444 if jwt_type is not None: 445 headers["typ"] = jwt_type 446 key, alg = self.jwt_key 447 encoded = encode(payload, key, algorithm=alg, headers=headers) 448 if self.encryption_key: 449 return self.encrypt(encoded) 450 return encoded 451 452 def encrypt(self, raw: str) -> str: 453 """Encrypt JWT""" 454 key = JWK.from_pem(self.encryption_key.certificate_data.encode()) 455 jwe = JWE( 456 raw, 457 json_encode( 458 { 459 "alg": "RSA-OAEP-256", 460 "enc": "A256CBC-HS512", 461 "typ": "JWE", 462 "kid": self.encryption_key.kid, 463 } 464 ), 465 ) 466 jwe.add_recipient(key) 467 return jwe.serialize(compact=True) 468 469 def webfinger(self, resource: str, request: HttpRequest): 470 return { 471 "subject": resource, 472 "links": [ 473 { 474 "rel": "http://openid.net/specs/connect/1.0/issuer", 475 "href": request.build_absolute_uri( 476 reverse( 477 "authentik_providers_oauth2:provider-root", 478 kwargs={ 479 "application_slug": self.application.slug, 480 }, 481 ) 482 ), 483 }, 484 ], 485 } 486 487 class Meta: 488 verbose_name = _("OAuth2/OpenID Provider") 489 verbose_name_plural = _("OAuth2/OpenID Providers") 490 491 492class BaseGrantModel(models.Model): 493 """Base Model for all grants""" 494 495 provider = models.ForeignKey(OAuth2Provider, on_delete=models.CASCADE) 496 user = models.ForeignKey(User, verbose_name=_("User"), on_delete=models.CASCADE) 497 revoked = models.BooleanField(default=False) 498 _scope = models.TextField(default="", verbose_name=_("Scopes")) 499 auth_time = models.DateTimeField(verbose_name="Authentication time") 500 session = models.ForeignKey( 501 AuthenticatedSession, null=True, on_delete=models.CASCADE, default=None 502 ) 503 504 class Meta: 505 abstract = True 506 507 @property 508 def scope(self) -> list[str]: 509 """Return scopes as list of strings""" 510 return self._scope.split() 511 512 @scope.setter 513 def scope(self, value): 514 self._scope = " ".join(value) 515 516 517class AuthorizationCode(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 518 """OAuth2 Authorization Code""" 519 520 code = models.CharField(max_length=255, unique=True, verbose_name=_("Code")) 521 nonce = models.TextField(null=True, default=None, verbose_name=_("Nonce")) 522 code_challenge = models.CharField(max_length=255, null=True, verbose_name=_("Code Challenge")) 523 code_challenge_method = models.CharField( 524 max_length=255, null=True, verbose_name=_("Code Challenge Method") 525 ) 526 527 class Meta: 528 verbose_name = _("Authorization Code") 529 verbose_name_plural = _("Authorization Codes") 530 indexes = ExpiringModel.Meta.indexes 531 532 def __str__(self): 533 return f"Authorization code for {self.provider_id} for user {self.user_id}" 534 535 @property 536 def serializer(self) -> Serializer: 537 from authentik.providers.oauth2.api.tokens import ExpiringBaseGrantModelSerializer 538 539 return ExpiringBaseGrantModelSerializer 540 541 @property 542 def c_hash(self): 543 """https://openid.net/specs/openid-connect-core-1_0.html#IDToken""" 544 hashed_code = sha256(self.code.encode("ascii")).hexdigest().encode("ascii") 545 return ( 546 base64.urlsafe_b64encode(binascii.unhexlify(hashed_code[: len(hashed_code) // 2])) 547 .rstrip(b"=") 548 .decode("ascii") 549 ) 550 551 552class AccessToken(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 553 """OAuth2 access token, non-opaque using a JWT as identifier""" 554 555 token = models.TextField() 556 _id_token = models.TextField() 557 558 class Meta: 559 indexes = ExpiringModel.Meta.indexes + [ 560 HashIndex(fields=["token"]), 561 ] 562 verbose_name = _("OAuth2 Access Token") 563 verbose_name_plural = _("OAuth2 Access Tokens") 564 565 def __str__(self): 566 return f"Access Token for {self.provider_id} for user {self.user_id}" 567 568 @property 569 def id_token(self) -> IDToken: 570 """Load ID Token from json""" 571 from authentik.providers.oauth2.id_token import IDToken 572 573 raw_token = json.loads(self._id_token) 574 return from_dict(IDToken, raw_token) 575 576 @id_token.setter 577 def id_token(self, value: IDToken): 578 self.token = value.to_access_token(self.provider, self) 579 self._id_token = json.dumps(asdict(value)) 580 581 @property 582 def at_hash(self): 583 """Get hashed access_token""" 584 hashed_access_token = sha256(self.token.encode("ascii")).hexdigest().encode("ascii") 585 return ( 586 base64.urlsafe_b64encode( 587 binascii.unhexlify(hashed_access_token[: len(hashed_access_token) // 2]) 588 ) 589 .rstrip(b"=") 590 .decode("ascii") 591 ) 592 593 @property 594 def serializer(self) -> Serializer: 595 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 596 597 return TokenModelSerializer 598 599 600class RefreshToken(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 601 """OAuth2 Refresh Token, opaque""" 602 603 token = models.TextField(default=generate_client_secret) 604 _id_token = models.TextField(verbose_name=_("ID Token")) 605 # Shadow the `session` field from `BaseGrantModel` as we want refresh tokens to persist even 606 # when the session is terminated. 607 session = models.ForeignKey( 608 AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None 609 ) 610 611 class Meta: 612 indexes = ExpiringModel.Meta.indexes + [ 613 HashIndex(fields=["token"]), 614 ] 615 verbose_name = _("OAuth2 Refresh Token") 616 verbose_name_plural = _("OAuth2 Refresh Tokens") 617 618 def __str__(self): 619 return f"Refresh Token for {self.provider_id} for user {self.user_id}" 620 621 @property 622 def id_token(self) -> IDToken: 623 """Load ID Token from json""" 624 from authentik.providers.oauth2.id_token import IDToken 625 626 raw_token = json.loads(self._id_token) 627 return from_dict(IDToken, raw_token) 628 629 @id_token.setter 630 def id_token(self, value: IDToken): 631 self._id_token = json.dumps(asdict(value)) 632 633 @property 634 def serializer(self) -> Serializer: 635 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 636 637 return TokenModelSerializer 638 639 640class DeviceToken(InternallyManagedMixin, ExpiringModel): 641 """Temporary device token for OAuth device flow""" 642 643 user = models.ForeignKey( 644 "authentik_core.User", default=None, on_delete=models.CASCADE, null=True 645 ) 646 provider = models.ForeignKey(OAuth2Provider, on_delete=models.CASCADE) 647 device_code = models.TextField(default=generate_key) 648 user_code = models.TextField(default=generate_code_fixed_length) 649 _scope = models.TextField(default="", verbose_name=_("Scopes")) 650 session = models.ForeignKey( 651 AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None 652 ) 653 654 @property 655 def scope(self) -> list[str]: 656 """Return scopes as list of strings""" 657 return self._scope.split() 658 659 @scope.setter 660 def scope(self, value): 661 self._scope = " ".join(value) 662 663 class Meta: 664 verbose_name = _("Device Token") 665 verbose_name_plural = _("Device Tokens") 666 indexes = ExpiringModel.Meta.indexes 667 668 def __str__(self): 669 return f"Device Token for {self.provider_id}"
67def generate_client_secret() -> str: 68 """Generate client secret with adequate length""" 69 return generate_id(128)
Generate client secret with adequate length
72class ClientType(models.TextChoices): 73 """Confidential clients are capable of maintaining the confidentiality 74 of their credentials. Public clients are incapable.""" 75 76 CONFIDENTIAL = "confidential", _("Confidential") 77 PUBLIC = "public", _("Public")
Confidential clients are capable of maintaining the confidentiality of their credentials. Public clients are incapable.
80class GrantType(models.TextChoices): 81 """OAuth2 Grant types we support""" 82 83 AUTHORIZATION_CODE = GRANT_TYPE_AUTHORIZATION_CODE 84 IMPLICIT = GRANT_TYPE_IMPLICIT 85 HYBRID = GRANT_TYPE_HYBRID 86 REFRESH_TOKEN = GRANT_TYPE_REFRESH_TOKEN 87 CLIENT_CREDENTIALS = GRANT_TYPE_CLIENT_CREDENTIALS 88 PASSWORD = GRANT_TYPE_PASSWORD 89 DEVICE_CODE = GRANT_TYPE_DEVICE_CODE
OAuth2 Grant types we support
98class ResponseMode(models.TextChoices): 99 """https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#OAuth.Post""" 100 101 QUERY = "query" 102 FRAGMENT = "fragment" 103 FORM_POST = "form_post"
106class IssuerMode(models.TextChoices): 107 """Configure how the `iss` field is created.""" 108 109 GLOBAL = "global", _("Same identifier is used for all providers") 110 PER_PROVIDER = ( 111 "per_provider", 112 _("Each provider has a different issuer, based on the application slug."), 113 )
Configure how the iss field is created.
116class RedirectURIMatchingMode(models.TextChoices): 117 STRICT = "strict", _("Strict URL comparison") 118 REGEX = "regex", _("Regular Expression URL matching")
Class for creating enumerated string choices.
121class RedirectURIType(models.TextChoices): 122 AUTHORIZATION = "authorization", _("Authorization") 123 LOGOUT = "logout", _("Logout")
Class for creating enumerated string choices.
126class OAuth2LogoutMethod(models.TextChoices): 127 """OAuth2/OIDC Logout methods""" 128 129 BACKCHANNEL = "backchannel", _("Back-channel") 130 FRONTCHANNEL = "frontchannel", _("Front-channel")
OAuth2/OIDC Logout methods
133@dataclass 134class RedirectURI: 135 """A single redirect URI entry""" 136 137 matching_mode: RedirectURIMatchingMode 138 url: str 139 redirect_uri_type: RedirectURIType = RedirectURIType.AUTHORIZATION
A single redirect URI entry
142class ResponseTypes(models.TextChoices): 143 """Response Type required by the client.""" 144 145 CODE = "code", _("code (Authorization Code Flow)") 146 ID_TOKEN = "id_token", _("id_token (Implicit Flow)") 147 ID_TOKEN_TOKEN = "id_token token", _("id_token token (Implicit Flow)") 148 CODE_TOKEN = "code token", _("code token (Hybrid Flow)") 149 CODE_ID_TOKEN = "code id_token", _("code id_token (Hybrid Flow)") 150 CODE_ID_TOKEN_TOKEN = "code id_token token", _("code id_token token (Hybrid Flow)")
Response Type required by the client.
153class JWTAlgorithms(models.TextChoices): 154 """Algorithm used to sign the JWT Token""" 155 156 HS256 = "HS256", _("HS256 (Symmetric Encryption)") 157 RS256 = "RS256", _("RS256 (Asymmetric Encryption)") 158 ES256 = "ES256", _("ES256 (Asymmetric Encryption)") 159 ES384 = "ES384", _("ES384 (Asymmetric Encryption)") 160 ES512 = "ES512", _("ES512 (Asymmetric Encryption)") 161 162 @classmethod 163 def from_private_key(cls, private_key: PrivateKeyTypes | None) -> str: 164 if isinstance(private_key, RSAPrivateKey): 165 return cls.RS256 166 if isinstance(private_key, EllipticCurvePrivateKey): 167 curve = private_key.curve 168 if isinstance(curve, SECP256R1): 169 return cls.ES256 170 if isinstance(curve, SECP384R1): 171 return cls.ES384 172 if isinstance(curve, SECP521R1): 173 return cls.ES512 174 raise ValueError(f"Invalid private key type: {type(private_key)}")
Algorithm used to sign the JWT Token
162 @classmethod 163 def from_private_key(cls, private_key: PrivateKeyTypes | None) -> str: 164 if isinstance(private_key, RSAPrivateKey): 165 return cls.RS256 166 if isinstance(private_key, EllipticCurvePrivateKey): 167 curve = private_key.curve 168 if isinstance(curve, SECP256R1): 169 return cls.ES256 170 if isinstance(curve, SECP384R1): 171 return cls.ES384 172 if isinstance(curve, SECP521R1): 173 return cls.ES512 174 raise ValueError(f"Invalid private key type: {type(private_key)}")
177class ScopeMapping(PropertyMapping): 178 """Map an OAuth Scope to users properties""" 179 180 scope_name = models.TextField(help_text=_("Scope used by the client")) 181 description = models.TextField( 182 blank=True, 183 help_text=_( 184 "Description shown to the user when consenting. " 185 "If left empty, the user won't be informed." 186 ), 187 ) 188 189 @property 190 def component(self) -> str: 191 return "ak-property-mapping-provider-scope-form" 192 193 @property 194 def serializer(self) -> type[Serializer]: 195 from authentik.providers.oauth2.api.scopes import ScopeMappingSerializer 196 197 return ScopeMappingSerializer 198 199 def __str__(self): 200 return f"Scope Mapping {self.name} ({self.scope_name})" 201 202 class Meta: 203 verbose_name = _("Scope Mapping") 204 verbose_name_plural = _("Scope Mappings")
Map an OAuth Scope to users properties
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
193 @property 194 def serializer(self) -> type[Serializer]: 195 from authentik.providers.oauth2.api.scopes import ScopeMappingSerializer 196 197 return ScopeMappingSerializer
Get serializer for this model
Accessor to the related object on the forward side of a one-to-one relation.
In the example::
class Restaurant(Model):
place = OneToOneField(Place, related_name='restaurant')
Restaurant.place is a ForwardOneToOneDescriptor instance.
Inherited Members
- authentik.core.models.PropertyMapping
- pm_uuid
- name
- expression
- objects
- evaluate
- managed
- provider_set
- source_userpropertymappings_set
- source_grouppropertymappings_set
- notificationwebhookmapping
- oauthsourcepropertymapping
- scopemapping
- endpoint_set
- racpropertymapping
- radiusproviderpropertymapping
- samlsourcepropertymapping
- samlpropertymapping
- scimprovider_set
- scimmapping
- kerberossourcepropertymapping
- ldapsourcepropertymapping
- plexsourcepropertymapping
- scimsourcepropertymapping
- telegramsourcepropertymapping
- googleworkspaceprovider_set
- googleworkspaceprovidermapping
- microsoftentraprovider_set
- microsoftentraprovidermapping
The requested object does not exist
The query returned multiple objects when only one was expected.
207class OAuth2Provider(WebfingerProvider, Provider): 208 """OAuth2 Provider for generic OAuth and OpenID Connect Applications.""" 209 210 client_type = models.CharField( 211 max_length=30, 212 choices=ClientType.choices, 213 default=ClientType.CONFIDENTIAL, 214 verbose_name=_("Client Type"), 215 help_text=_( 216 "Confidential clients are capable of maintaining the confidentiality " 217 "of their credentials. Public clients are incapable" 218 ), 219 ) 220 grant_types = ArrayField(models.TextField(choices=GrantType.choices), default=list) 221 client_id = models.CharField( 222 max_length=255, 223 unique=True, 224 verbose_name=_("Client ID"), 225 default=generate_id, 226 ) 227 client_secret = models.CharField( 228 max_length=255, 229 blank=True, 230 verbose_name=_("Client Secret"), 231 default=generate_client_secret, 232 ) 233 _redirect_uris = models.JSONField( 234 default=list, 235 verbose_name=_("Redirect URIs"), 236 ) 237 logout_uri = models.TextField( 238 validators=[DomainlessURLValidator(schemes=("http", "https"))], 239 verbose_name=_("Logout URI"), 240 blank=True, 241 ) 242 logout_method = models.TextField( 243 choices=OAuth2LogoutMethod.choices, 244 default=OAuth2LogoutMethod.BACKCHANNEL, 245 verbose_name=_("Logout Method"), 246 help_text=_( 247 "Backchannel logs out with server to server calls. " 248 "Frontchannel uses iframes in your browser" 249 ), 250 ) 251 include_claims_in_id_token = models.BooleanField( 252 default=True, 253 verbose_name=_("Include claims in id_token"), 254 help_text=_( 255 "Include User claims from scopes in the id_token, for applications " 256 "that don't access the userinfo endpoint." 257 ), 258 ) 259 260 access_code_validity = models.TextField( 261 default="minutes=1", 262 validators=[timedelta_string_validator], 263 help_text=_( 264 "Access codes not valid on or after current time + this value " 265 "(Format: hours=1;minutes=2;seconds=3)." 266 ), 267 ) 268 access_token_validity = models.TextField( 269 default="hours=1", 270 validators=[timedelta_string_validator], 271 help_text=_( 272 "Tokens not valid on or after current time + this value " 273 "(Format: hours=1;minutes=2;seconds=3)." 274 ), 275 ) 276 refresh_token_validity = models.TextField( 277 default="days=30", 278 validators=[timedelta_string_validator], 279 help_text=_( 280 "Tokens not valid on or after current time + this value " 281 "(Format: hours=1;minutes=2;seconds=3)." 282 ), 283 ) 284 refresh_token_threshold = models.TextField( 285 default="seconds=0", 286 validators=[timedelta_string_validator], 287 help_text=_( 288 "When refreshing a token, if the refresh token is valid for less than " 289 "this duration, it will be renewed. " 290 "When set to seconds=0, token will always be renewed. " 291 "(Format: hours=1;minutes=2;seconds=3)." 292 ), 293 ) 294 295 sub_mode = models.TextField( 296 choices=SubModes.choices, 297 default=SubModes.HASHED_USER_ID, 298 help_text=_( 299 "Configure what data should be used as unique User Identifier. For most cases, " 300 "the default should be fine." 301 ), 302 ) 303 issuer_mode = models.TextField( 304 choices=IssuerMode.choices, 305 default=IssuerMode.PER_PROVIDER, 306 help_text=_("Configure how the issuer field of the ID Token should be filled."), 307 ) 308 309 signing_key = models.ForeignKey( 310 CertificateKeyPair, 311 verbose_name=_("Signing Key"), 312 on_delete=models.SET_NULL, 313 null=True, 314 help_text=_("Key used to sign the tokens."), 315 related_name="oauth2provider_signing_key_set", 316 ) 317 encryption_key = models.ForeignKey( 318 CertificateKeyPair, 319 verbose_name=_("Encryption Key"), 320 on_delete=models.SET_NULL, 321 null=True, 322 help_text=_( 323 "Key used to encrypt the tokens. When set, " 324 "tokens will be encrypted and returned as JWEs." 325 ), 326 related_name="oauth2provider_encryption_key_set", 327 ) 328 329 jwt_federation_sources = models.ManyToManyField( 330 OAuthSource, 331 verbose_name=_( 332 "Any JWT signed by the JWK of the selected source can be used to authenticate." 333 ), 334 related_name="oauth2_providers", 335 default=None, 336 blank=True, 337 ) 338 jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None) 339 340 @cached_property 341 def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]: 342 """Get either the configured certificate or the client secret""" 343 if not self.signing_key: 344 # No Certificate at all, assume HS256 345 return self.client_secret, JWTAlgorithms.HS256 346 key: CertificateKeyPair = self.signing_key 347 private_key = key.private_key 348 return private_key, JWTAlgorithms.from_private_key(private_key) 349 350 def get_issuer(self, request: HttpRequest) -> str | None: 351 """Get issuer, based on request""" 352 if self.issuer_mode == IssuerMode.GLOBAL: 353 return request.build_absolute_uri(reverse("authentik_core:root-redirect")) 354 try: 355 url = reverse( 356 "authentik_providers_oauth2:provider-root", 357 kwargs={ 358 "application_slug": self.application.slug, 359 }, 360 ) 361 return request.build_absolute_uri(url) 362 except Provider.application.RelatedObjectDoesNotExist: 363 return None 364 365 @property 366 def redirect_uris(self) -> list[RedirectURI]: 367 uris = [] 368 for entry in self._redirect_uris: 369 uris.append( 370 from_dict( 371 RedirectURI, 372 entry, 373 config=Config( 374 type_hooks={ 375 RedirectURIMatchingMode: RedirectURIMatchingMode, 376 RedirectURIType: RedirectURIType, 377 } 378 ), 379 ) 380 ) 381 return uris 382 383 @redirect_uris.setter 384 def redirect_uris(self, value: list[RedirectURI]): 385 cleansed = [] 386 for entry in value: 387 cleansed.append(asdict(entry)) 388 self._redirect_uris = cleansed 389 390 @property 391 def authorization_redirect_uris(self) -> list[RedirectURI]: 392 return [ 393 uri 394 for uri in self.redirect_uris 395 if uri.redirect_uri_type == RedirectURIType.AUTHORIZATION 396 ] 397 398 @property 399 def post_logout_redirect_uris(self) -> list[RedirectURI]: 400 return [ 401 uri for uri in self.redirect_uris if uri.redirect_uri_type == RedirectURIType.LOGOUT 402 ] 403 404 @property 405 def launch_url(self) -> str | None: 406 """Guess launch_url based on first redirect_uri""" 407 redirects = self.authorization_redirect_uris 408 if len(redirects) < 1: 409 return None 410 main_url = redirects[0].url 411 try: 412 launch_url = urlparse(main_url)._replace(path="") 413 return urlunparse(launch_url) 414 except ValueError as exc: 415 LOGGER.warning("Failed to format launch url", exc=exc) 416 return None 417 418 @property 419 def icon_url(self) -> str | None: 420 return static("authentik/sources/openidconnect.svg") 421 422 @property 423 def component(self) -> str: 424 return "ak-provider-oauth2-form" 425 426 @property 427 def serializer(self) -> type[Serializer]: 428 from authentik.providers.oauth2.api.providers import OAuth2ProviderSerializer 429 430 return OAuth2ProviderSerializer 431 432 def __str__(self): 433 return f"OAuth2 Provider {self.name}" 434 435 def encode(self, payload: dict[str, Any], jwt_type: str | None = None) -> str: 436 """Represent the ID Token as a JSON Web Token (JWT). 437 438 :param payload The payload to encode into the JWT 439 :param jwt_type The type of the JWT. This will be put in the JWT header using the `typ` 440 parameter. See RFC7515 Section 4.1.9. If not set fallback to the default of `JWT`. 441 """ 442 headers = {} 443 if self.signing_key: 444 headers["kid"] = self.signing_key.kid 445 if jwt_type is not None: 446 headers["typ"] = jwt_type 447 key, alg = self.jwt_key 448 encoded = encode(payload, key, algorithm=alg, headers=headers) 449 if self.encryption_key: 450 return self.encrypt(encoded) 451 return encoded 452 453 def encrypt(self, raw: str) -> str: 454 """Encrypt JWT""" 455 key = JWK.from_pem(self.encryption_key.certificate_data.encode()) 456 jwe = JWE( 457 raw, 458 json_encode( 459 { 460 "alg": "RSA-OAEP-256", 461 "enc": "A256CBC-HS512", 462 "typ": "JWE", 463 "kid": self.encryption_key.kid, 464 } 465 ), 466 ) 467 jwe.add_recipient(key) 468 return jwe.serialize(compact=True) 469 470 def webfinger(self, resource: str, request: HttpRequest): 471 return { 472 "subject": resource, 473 "links": [ 474 { 475 "rel": "http://openid.net/specs/connect/1.0/issuer", 476 "href": request.build_absolute_uri( 477 reverse( 478 "authentik_providers_oauth2:provider-root", 479 kwargs={ 480 "application_slug": self.application.slug, 481 }, 482 ) 483 ), 484 }, 485 ], 486 } 487 488 class Meta: 489 verbose_name = _("OAuth2/OpenID Provider") 490 verbose_name_plural = _("OAuth2/OpenID Providers")
OAuth2 Provider for generic OAuth and OpenID Connect Applications.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
340 @cached_property 341 def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]: 342 """Get either the configured certificate or the client secret""" 343 if not self.signing_key: 344 # No Certificate at all, assume HS256 345 return self.client_secret, JWTAlgorithms.HS256 346 key: CertificateKeyPair = self.signing_key 347 private_key = key.private_key 348 return private_key, JWTAlgorithms.from_private_key(private_key)
Get either the configured certificate or the client secret
350 def get_issuer(self, request: HttpRequest) -> str | None: 351 """Get issuer, based on request""" 352 if self.issuer_mode == IssuerMode.GLOBAL: 353 return request.build_absolute_uri(reverse("authentik_core:root-redirect")) 354 try: 355 url = reverse( 356 "authentik_providers_oauth2:provider-root", 357 kwargs={ 358 "application_slug": self.application.slug, 359 }, 360 ) 361 return request.build_absolute_uri(url) 362 except Provider.application.RelatedObjectDoesNotExist: 363 return None
Get issuer, based on request
365 @property 366 def redirect_uris(self) -> list[RedirectURI]: 367 uris = [] 368 for entry in self._redirect_uris: 369 uris.append( 370 from_dict( 371 RedirectURI, 372 entry, 373 config=Config( 374 type_hooks={ 375 RedirectURIMatchingMode: RedirectURIMatchingMode, 376 RedirectURIType: RedirectURIType, 377 } 378 ), 379 ) 380 ) 381 return uris
404 @property 405 def launch_url(self) -> str | None: 406 """Guess launch_url based on first redirect_uri""" 407 redirects = self.authorization_redirect_uris 408 if len(redirects) < 1: 409 return None 410 main_url = redirects[0].url 411 try: 412 launch_url = urlparse(main_url)._replace(path="") 413 return urlunparse(launch_url) 414 except ValueError as exc: 415 LOGGER.warning("Failed to format launch url", exc=exc) 416 return None
Guess launch_url based on first redirect_uri
426 @property 427 def serializer(self) -> type[Serializer]: 428 from authentik.providers.oauth2.api.providers import OAuth2ProviderSerializer 429 430 return OAuth2ProviderSerializer
Get serializer for this model
435 def encode(self, payload: dict[str, Any], jwt_type: str | None = None) -> str: 436 """Represent the ID Token as a JSON Web Token (JWT). 437 438 :param payload The payload to encode into the JWT 439 :param jwt_type The type of the JWT. This will be put in the JWT header using the `typ` 440 parameter. See RFC7515 Section 4.1.9. If not set fallback to the default of `JWT`. 441 """ 442 headers = {} 443 if self.signing_key: 444 headers["kid"] = self.signing_key.kid 445 if jwt_type is not None: 446 headers["typ"] = jwt_type 447 key, alg = self.jwt_key 448 encoded = encode(payload, key, algorithm=alg, headers=headers) 449 if self.encryption_key: 450 return self.encrypt(encoded) 451 return encoded
Represent the ID Token as a JSON Web Token (JWT).
:param payload The payload to encode into the JWT
:param jwt_type The type of the JWT. This will be put in the JWT header using the typ
parameter. See RFC7515 Section 4.1.9. If not set fallback to the default of JWT.
453 def encrypt(self, raw: str) -> str: 454 """Encrypt JWT""" 455 key = JWK.from_pem(self.encryption_key.certificate_data.encode()) 456 jwe = JWE( 457 raw, 458 json_encode( 459 { 460 "alg": "RSA-OAEP-256", 461 "enc": "A256CBC-HS512", 462 "typ": "JWE", 463 "kid": self.encryption_key.kid, 464 } 465 ), 466 ) 467 jwe.add_recipient(key) 468 return jwe.serialize(compact=True)
Encrypt JWT
470 def webfinger(self, resource: str, request: HttpRequest): 471 return { 472 "subject": resource, 473 "links": [ 474 { 475 "rel": "http://openid.net/specs/connect/1.0/issuer", 476 "href": request.build_absolute_uri( 477 reverse( 478 "authentik_providers_oauth2:provider-root", 479 kwargs={ 480 "application_slug": self.application.slug, 481 }, 482 ) 483 ), 484 }, 485 ], 486 }
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Accessor to the related object on the forward side of a one-to-one relation.
In the example::
class Restaurant(Model):
place = OneToOneField(Place, related_name='restaurant')
Restaurant.place is a ForwardOneToOneDescriptor instance.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related object on the reverse side of a one-to-one relation.
In the example::
class Restaurant(Model):
place = OneToOneField(Place, related_name='restaurant')
Place.restaurant is a ReverseOneToOneDescriptor instance.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Inherited Members
- authentik.core.models.Provider
- name
- authentication_flow
- invalidation_flow
- property_mappings
- backchannel_application
- is_backchannel
- objects
- authentication_flow_id
- invalidation_flow_id
- backchannel_application_id
- id
- application
- outpost_set
- oauth2provider
- ldapprovider
- racprovider
- radiusprovider
- samlprovider
- scimprovider
- googleworkspaceprovider
- microsoftentraprovider
- ssfprovider
The requested object does not exist
The query returned multiple objects when only one was expected.
493class BaseGrantModel(models.Model): 494 """Base Model for all grants""" 495 496 provider = models.ForeignKey(OAuth2Provider, on_delete=models.CASCADE) 497 user = models.ForeignKey(User, verbose_name=_("User"), on_delete=models.CASCADE) 498 revoked = models.BooleanField(default=False) 499 _scope = models.TextField(default="", verbose_name=_("Scopes")) 500 auth_time = models.DateTimeField(verbose_name="Authentication time") 501 session = models.ForeignKey( 502 AuthenticatedSession, null=True, on_delete=models.CASCADE, default=None 503 ) 504 505 class Meta: 506 abstract = True 507 508 @property 509 def scope(self) -> list[str]: 510 """Return scopes as list of strings""" 511 return self._scope.split() 512 513 @scope.setter 514 def scope(self, value): 515 self._scope = " ".join(value)
Base Model for all grants
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
508 @property 509 def scope(self) -> list[str]: 510 """Return scopes as list of strings""" 511 return self._scope.split()
Return scopes as list of strings
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
518class AuthorizationCode(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 519 """OAuth2 Authorization Code""" 520 521 code = models.CharField(max_length=255, unique=True, verbose_name=_("Code")) 522 nonce = models.TextField(null=True, default=None, verbose_name=_("Nonce")) 523 code_challenge = models.CharField(max_length=255, null=True, verbose_name=_("Code Challenge")) 524 code_challenge_method = models.CharField( 525 max_length=255, null=True, verbose_name=_("Code Challenge Method") 526 ) 527 528 class Meta: 529 verbose_name = _("Authorization Code") 530 verbose_name_plural = _("Authorization Codes") 531 indexes = ExpiringModel.Meta.indexes 532 533 def __str__(self): 534 return f"Authorization code for {self.provider_id} for user {self.user_id}" 535 536 @property 537 def serializer(self) -> Serializer: 538 from authentik.providers.oauth2.api.tokens import ExpiringBaseGrantModelSerializer 539 540 return ExpiringBaseGrantModelSerializer 541 542 @property 543 def c_hash(self): 544 """https://openid.net/specs/openid-connect-core-1_0.html#IDToken""" 545 hashed_code = sha256(self.code.encode("ascii")).hexdigest().encode("ascii") 546 return ( 547 base64.urlsafe_b64encode(binascii.unhexlify(hashed_code[: len(hashed_code) // 2])) 548 .rstrip(b"=") 549 .decode("ascii") 550 )
OAuth2 Authorization Code
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
536 @property 537 def serializer(self) -> Serializer: 538 from authentik.providers.oauth2.api.tokens import ExpiringBaseGrantModelSerializer 539 540 return ExpiringBaseGrantModelSerializer
Get serializer for this model
542 @property 543 def c_hash(self): 544 """https://openid.net/specs/openid-connect-core-1_0.html#IDToken""" 545 hashed_code = sha256(self.code.encode("ascii")).hexdigest().encode("ascii") 546 return ( 547 base64.urlsafe_b64encode(binascii.unhexlify(hashed_code[: len(hashed_code) // 2])) 548 .rstrip(b"=") 549 .decode("ascii") 550 )
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
The requested object does not exist
The query returned multiple objects when only one was expected.
553class AccessToken(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 554 """OAuth2 access token, non-opaque using a JWT as identifier""" 555 556 token = models.TextField() 557 _id_token = models.TextField() 558 559 class Meta: 560 indexes = ExpiringModel.Meta.indexes + [ 561 HashIndex(fields=["token"]), 562 ] 563 verbose_name = _("OAuth2 Access Token") 564 verbose_name_plural = _("OAuth2 Access Tokens") 565 566 def __str__(self): 567 return f"Access Token for {self.provider_id} for user {self.user_id}" 568 569 @property 570 def id_token(self) -> IDToken: 571 """Load ID Token from json""" 572 from authentik.providers.oauth2.id_token import IDToken 573 574 raw_token = json.loads(self._id_token) 575 return from_dict(IDToken, raw_token) 576 577 @id_token.setter 578 def id_token(self, value: IDToken): 579 self.token = value.to_access_token(self.provider, self) 580 self._id_token = json.dumps(asdict(value)) 581 582 @property 583 def at_hash(self): 584 """Get hashed access_token""" 585 hashed_access_token = sha256(self.token.encode("ascii")).hexdigest().encode("ascii") 586 return ( 587 base64.urlsafe_b64encode( 588 binascii.unhexlify(hashed_access_token[: len(hashed_access_token) // 2]) 589 ) 590 .rstrip(b"=") 591 .decode("ascii") 592 ) 593 594 @property 595 def serializer(self) -> Serializer: 596 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 597 598 return TokenModelSerializer
OAuth2 access token, non-opaque using a JWT as identifier
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
569 @property 570 def id_token(self) -> IDToken: 571 """Load ID Token from json""" 572 from authentik.providers.oauth2.id_token import IDToken 573 574 raw_token = json.loads(self._id_token) 575 return from_dict(IDToken, raw_token)
Load ID Token from json
582 @property 583 def at_hash(self): 584 """Get hashed access_token""" 585 hashed_access_token = sha256(self.token.encode("ascii")).hexdigest().encode("ascii") 586 return ( 587 base64.urlsafe_b64encode( 588 binascii.unhexlify(hashed_access_token[: len(hashed_access_token) // 2]) 589 ) 590 .rstrip(b"=") 591 .decode("ascii") 592 )
Get hashed access_token
594 @property 595 def serializer(self) -> Serializer: 596 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 597 598 return TokenModelSerializer
Get serializer for this model
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
The requested object does not exist
The query returned multiple objects when only one was expected.
601class RefreshToken(InternallyManagedMixin, SerializerModel, ExpiringModel, BaseGrantModel): 602 """OAuth2 Refresh Token, opaque""" 603 604 token = models.TextField(default=generate_client_secret) 605 _id_token = models.TextField(verbose_name=_("ID Token")) 606 # Shadow the `session` field from `BaseGrantModel` as we want refresh tokens to persist even 607 # when the session is terminated. 608 session = models.ForeignKey( 609 AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None 610 ) 611 612 class Meta: 613 indexes = ExpiringModel.Meta.indexes + [ 614 HashIndex(fields=["token"]), 615 ] 616 verbose_name = _("OAuth2 Refresh Token") 617 verbose_name_plural = _("OAuth2 Refresh Tokens") 618 619 def __str__(self): 620 return f"Refresh Token for {self.provider_id} for user {self.user_id}" 621 622 @property 623 def id_token(self) -> IDToken: 624 """Load ID Token from json""" 625 from authentik.providers.oauth2.id_token import IDToken 626 627 raw_token = json.loads(self._id_token) 628 return from_dict(IDToken, raw_token) 629 630 @id_token.setter 631 def id_token(self, value: IDToken): 632 self._id_token = json.dumps(asdict(value)) 633 634 @property 635 def serializer(self) -> Serializer: 636 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 637 638 return TokenModelSerializer
OAuth2 Refresh Token, opaque
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
622 @property 623 def id_token(self) -> IDToken: 624 """Load ID Token from json""" 625 from authentik.providers.oauth2.id_token import IDToken 626 627 raw_token = json.loads(self._id_token) 628 return from_dict(IDToken, raw_token)
Load ID Token from json
634 @property 635 def serializer(self) -> Serializer: 636 from authentik.providers.oauth2.api.tokens import TokenModelSerializer 637 638 return TokenModelSerializer
Get serializer for this model
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
The requested object does not exist
The query returned multiple objects when only one was expected.
641class DeviceToken(InternallyManagedMixin, ExpiringModel): 642 """Temporary device token for OAuth device flow""" 643 644 user = models.ForeignKey( 645 "authentik_core.User", default=None, on_delete=models.CASCADE, null=True 646 ) 647 provider = models.ForeignKey(OAuth2Provider, on_delete=models.CASCADE) 648 device_code = models.TextField(default=generate_key) 649 user_code = models.TextField(default=generate_code_fixed_length) 650 _scope = models.TextField(default="", verbose_name=_("Scopes")) 651 session = models.ForeignKey( 652 AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None 653 ) 654 655 @property 656 def scope(self) -> list[str]: 657 """Return scopes as list of strings""" 658 return self._scope.split() 659 660 @scope.setter 661 def scope(self, value): 662 self._scope = " ".join(value) 663 664 class Meta: 665 verbose_name = _("Device Token") 666 verbose_name_plural = _("Device Tokens") 667 indexes = ExpiringModel.Meta.indexes 668 669 def __str__(self): 670 return f"Device Token for {self.provider_id}"
Temporary device token for OAuth device flow
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
655 @property 656 def scope(self) -> list[str]: 657 """Return scopes as list of strings""" 658 return self._scope.split()
Return scopes as list of strings
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Inherited Members
The requested object does not exist
The query returned multiple objects when only one was expected.