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}"
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
def generate_client_secret() -> str:
67def generate_client_secret() -> str:
68    """Generate client secret with adequate length"""
69    return generate_id(128)

Generate client secret with adequate length

class ClientType(django.db.models.enums.TextChoices):
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.

CONFIDENTIAL = ClientType.CONFIDENTIAL
class GrantType(django.db.models.enums.TextChoices):
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

AUTHORIZATION_CODE = GrantType.AUTHORIZATION_CODE
IMPLICIT = GrantType.IMPLICIT
HYBRID = GrantType.HYBRID
REFRESH_TOKEN = GrantType.REFRESH_TOKEN
CLIENT_CREDENTIALS = GrantType.CLIENT_CREDENTIALS
PASSWORD = GrantType.PASSWORD
DEVICE_CODE = GrantType.DEVICE_CODE
GrantTypes = <enum 'GrantType'>
class ResponseMode(django.db.models.enums.TextChoices):
 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"
class IssuerMode(django.db.models.enums.TextChoices):
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.

PER_PROVIDER = IssuerMode.PER_PROVIDER
class RedirectURIMatchingMode(django.db.models.enums.TextChoices):
116class RedirectURIMatchingMode(models.TextChoices):
117    STRICT = "strict", _("Strict URL comparison")
118    REGEX = "regex", _("Regular Expression URL matching")

Class for creating enumerated string choices.

class RedirectURIType(django.db.models.enums.TextChoices):
121class RedirectURIType(models.TextChoices):
122    AUTHORIZATION = "authorization", _("Authorization")
123    LOGOUT = "logout", _("Logout")

Class for creating enumerated string choices.

class OAuth2LogoutMethod(django.db.models.enums.TextChoices):
126class OAuth2LogoutMethod(models.TextChoices):
127    """OAuth2/OIDC Logout methods"""
128
129    BACKCHANNEL = "backchannel", _("Back-channel")
130    FRONTCHANNEL = "frontchannel", _("Front-channel")

OAuth2/OIDC Logout methods

@dataclass
class RedirectURI:
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

RedirectURI( matching_mode: RedirectURIMatchingMode, url: str, redirect_uri_type: RedirectURIType = RedirectURIType.AUTHORIZATION)
matching_mode: RedirectURIMatchingMode
url: str
class ResponseTypes(django.db.models.enums.TextChoices):
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.

ID_TOKEN_TOKEN = ResponseTypes.ID_TOKEN_TOKEN
CODE_ID_TOKEN = ResponseTypes.CODE_ID_TOKEN
CODE_ID_TOKEN_TOKEN = ResponseTypes.CODE_ID_TOKEN_TOKEN
class JWTAlgorithms(django.db.models.enums.TextChoices):
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

@classmethod
def from_private_key( cls, private_key: cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey | cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey | cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA44PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA65PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA87PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM768PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM1024PrivateKey | cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey | cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey | cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey | cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey | cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey | None) -> str:
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)}")
class ScopeMapping(authentik.core.models.PropertyMapping):
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

def scope_name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def description(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

component: str
189    @property
190    def component(self) -> str:
191        return "ak-property-mapping-provider-scope-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.Serializer]
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

propertymapping_ptr_id
propertymapping_ptr

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.

class ScopeMapping.DoesNotExist(authentik.core.models.PropertyMapping.DoesNotExist):

The requested object does not exist

class ScopeMapping.MultipleObjectsReturned(authentik.core.models.PropertyMapping.MultipleObjectsReturned):

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.

def client_type(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def grant_types(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def client_id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def client_secret(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def logout_uri(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def logout_method(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def include_claims_in_id_token(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def access_code_validity(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def access_token_validity(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def refresh_token_validity(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def refresh_token_threshold(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def sub_mode(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def issuer_mode(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

signing_key

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.

encryption_key

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.

jwt_federation_sources

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.

jwt_federation_providers

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.

jwt_key: tuple[str | cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey | cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey | cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA44PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA65PrivateKey | cryptography.hazmat.primitives.asymmetric.mldsa.MLDSA87PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM768PrivateKey | cryptography.hazmat.primitives.asymmetric.mlkem.MLKEM1024PrivateKey | cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey | cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey | cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey | cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey | cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey, str]
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

def get_issuer(self, request: django.http.request.HttpRequest) -> str | None:
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

redirect_uris: list[RedirectURI]
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
authorization_redirect_uris: list[RedirectURI]
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        ]
post_logout_redirect_uris: list[RedirectURI]
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        ]
launch_url: str | None
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

icon_url: str | None
418    @property
419    def icon_url(self) -> str | None:
420        return static("authentik/sources/openidconnect.svg")
component: str
422    @property
423    def component(self) -> str:
424        return "ak-provider-oauth2-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.Serializer]
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

def encode(self, payload: dict[str, typing.Any], jwt_type: str | None = None) -> str:
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.

def encrypt(self, raw: str) -> str:
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

def webfinger(self, resource: str, request: django.http.request.HttpRequest):
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        }
def get_client_type_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_logout_method_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_sub_mode_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_issuer_mode_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

signing_key_id
encryption_key_id
provider_ptr_id
provider_ptr

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.

agentconnector_set

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.

oauth2provider_set

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.

authorizationcode_set

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.

accesstoken_set

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.

refreshtoken_set

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.

devicetoken_set

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.

proxyprovider

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.

ssfprovider_set

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.

class OAuth2Provider.DoesNotExist(authentik.core.models.Provider.DoesNotExist):

The requested object does not exist

class OAuth2Provider.MultipleObjectsReturned(authentik.core.models.Provider.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class BaseGrantModel(django.db.models.base.Model):
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

provider

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.

user

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.

def revoked(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def auth_time(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session

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.

scope: list[str]
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

provider_id
user_id
def get_next_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

session_id
class BaseGrantModel.Meta:
505    class Meta:
506        abstract = True
abstract = False
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

def code(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def nonce(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def code_challenge(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def code_challenge_method(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

serializer: rest_framework.serializers.Serializer
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

c_hash
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        )
def expires(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expiring(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

provider

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.

user

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.

def revoked(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def auth_time(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session

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.

provider_id
user_id
def get_next_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

session_id
def id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class AuthorizationCode.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class AuthorizationCode.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

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

def token(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

id_token
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

at_hash
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

serializer: rest_framework.serializers.Serializer
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

def expires(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expiring(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

provider

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.

user

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.

def revoked(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def auth_time(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session

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.

provider_id
user_id
def get_next_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

session_id
def id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class AccessToken.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class AccessToken.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

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

def token(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session

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.

id_token
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

serializer: rest_framework.serializers.Serializer
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

def expires(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expiring(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

provider

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.

user

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.

def revoked(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def auth_time(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session_id
provider_id
user_id
def get_next_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_auth_time(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class RefreshToken.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class RefreshToken.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

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

user

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.

provider

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.

def device_code(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def user_code(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

session

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.

scope: list[str]
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

def expires(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def expiring(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

user_id
provider_id
session_id
def id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class DeviceToken.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class DeviceToken.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.