authentik.stages.authenticator_totp.models
OTP Time-based models
1"""OTP Time-based models""" 2 3import time 4from base64 import b32encode 5from binascii import unhexlify 6from urllib.parse import quote, urlencode 7 8from django.conf import settings 9from django.db import models 10from django.utils.translation import gettext_lazy as _ 11from django.views import View 12from rest_framework.serializers import BaseSerializer 13 14from authentik.core.types import UserSettingSerializer 15from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage 16from authentik.lib.models import SerializerModel 17from authentik.stages.authenticator.models import Device, ThrottlingMixin 18from authentik.stages.authenticator.oath import TOTP 19from authentik.stages.authenticator.util import hex_validator, random_hex 20 21 22class TOTPDigits(models.TextChoices): 23 """OTP Time Digits""" 24 25 SIX = "6", _("6 digits, widely compatible") 26 EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator") 27 28 29class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage): 30 """Setup Time-based OTP authentication for the user.""" 31 32 digits = models.IntegerField(choices=TOTPDigits.choices) 33 34 @property 35 def serializer(self) -> type[BaseSerializer]: 36 from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer 37 38 return AuthenticatorTOTPStageSerializer 39 40 @property 41 def view(self) -> type[View]: 42 from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView 43 44 return AuthenticatorTOTPStageView 45 46 @property 47 def component(self) -> str: 48 return "ak-stage-authenticator-totp-form" 49 50 def ui_user_settings(self) -> UserSettingSerializer | None: 51 return UserSettingSerializer( 52 data={ 53 "title": self.friendly_name or str(self._meta.verbose_name), 54 "component": "ak-user-settings-authenticator-totp", 55 } 56 ) 57 58 def __str__(self) -> str: 59 return f"TOTP Authenticator Setup Stage {self.name}" 60 61 class Meta: 62 verbose_name = _("TOTP Authenticator Setup Stage") 63 verbose_name_plural = _("TOTP Authenticator Setup Stages") 64 65 66def default_key(): 67 """Default TOTP Device key""" 68 return random_hex(20) 69 70 71def key_validator(value): 72 """Validate totp key""" 73 return hex_validator()(value) 74 75 76class TOTPDevice(SerializerModel, ThrottlingMixin, Device): 77 """ 78 A generic TOTP :class:`~authentik.stages.authenticator.models.Device`. The model fields mostly 79 correspond to the arguments to :func:`authentik.stages.authenticator.oath.totp`. They all have 80 sensible defaults, including the key, which is randomly generated. 81 82 .. attribute:: key 83 84 *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20 85 random bytes) 86 87 .. attribute:: step 88 89 *PositiveSmallIntegerField*: The time step in seconds. (Default: 30) 90 91 .. attribute:: t0 92 93 *BigIntegerField*: The Unix time at which to begin counting steps. 94 (Default: 0) 95 96 .. attribute:: digits 97 98 *PositiveSmallIntegerField*: The number of digits to expect in a token 99 (6 or 8). (Default: 6) 100 101 .. attribute:: tolerance 102 103 *PositiveSmallIntegerField*: The number of time steps in the past or 104 future to allow. For example, if this is 1, we'll accept any of three 105 tokens: the current one, the previous one, and the next one. (Default: 106 1) 107 108 .. attribute:: drift 109 110 *SmallIntegerField*: The number of time steps the prover is known to 111 deviate from our clock. If :setting:`OTP_TOTP_SYNC` is ``True``, we'll 112 update this any time we match a token that is not the current one. 113 (Default: 0) 114 115 .. attribute:: last_t 116 117 *BigIntegerField*: The time step of the last verified token. To avoid 118 verifying the same token twice, this will be updated on each successful 119 verification. Only tokens at a higher time step will be verified 120 subsequently. (Default: -1) 121 122 """ 123 124 key = models.CharField( 125 max_length=80, 126 validators=[key_validator], 127 default=default_key, 128 help_text="A hex-encoded secret key of up to 40 bytes.", 129 ) 130 step = models.PositiveSmallIntegerField(default=30, help_text="The time step in seconds.") 131 t0 = models.BigIntegerField( 132 default=0, help_text="The Unix time at which to begin counting steps." 133 ) 134 digits = models.PositiveSmallIntegerField( 135 choices=[(6, 6), (8, 8)], 136 default=6, 137 help_text="The number of digits to expect in a token.", 138 ) 139 tolerance = models.PositiveSmallIntegerField( 140 default=1, help_text="The number of time steps in the past or future to allow." 141 ) 142 drift = models.SmallIntegerField( 143 default=0, 144 help_text="The number of time steps the prover is known to deviate from our clock.", 145 ) 146 last_t = models.BigIntegerField( 147 default=-1, 148 help_text=( 149 "The t value of the latest verified token. " 150 "The next token must be at a higher time step." 151 ), 152 ) 153 154 @property 155 def serializer(self) -> type[BaseSerializer]: 156 from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer 157 158 return TOTPDeviceSerializer 159 160 @property 161 def bin_key(self): 162 """ 163 The secret key as a binary string. 164 """ 165 return unhexlify(self.key.encode()) 166 167 def verify_token(self, token): 168 otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True) 169 170 verify_allowed, _ = self.verify_is_allowed() 171 if not verify_allowed: 172 return False 173 174 try: 175 token = int(token) 176 except ValueError: 177 verified = False 178 else: 179 key = self.bin_key 180 181 totp = TOTP(key, self.step, self.t0, self.digits, self.drift) 182 totp.time = time.time() 183 184 verified = totp.verify(token, self.tolerance, self.last_t + 1) 185 if verified: 186 self.last_t = totp.t() 187 if otp_totp_sync: 188 self.drift = totp.drift 189 self.throttle_reset(commit=False) 190 self.save() 191 192 if not verified: 193 self.throttle_increment(commit=True) 194 195 return verified 196 197 def get_throttle_factor(self): 198 return getattr(settings, "OTP_TOTP_THROTTLE_FACTOR", 1) 199 200 @property 201 def config_url(self): 202 """ 203 A URL for configuring Google Authenticator or similar. 204 205 See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. 206 The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available. 207 The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available. 208 209 """ 210 label = str(self.user.username) 211 params = { 212 "secret": b32encode(self.bin_key), 213 "algorithm": "SHA1", 214 "digits": self.digits, 215 "period": self.step, 216 } 217 urlencoded_params = urlencode(params) 218 219 issuer = self._read_str_from_settings("OTP_TOTP_ISSUER") 220 if issuer: 221 issuer = issuer.replace(":", "") 222 label = f"{issuer}:{label}" 223 urlencoded_params += ( 224 f"&issuer={quote(issuer)}" # encode issuer as per RFC 3986, not quote_plus 225 ) 226 227 image = self._read_str_from_settings("OTP_TOTP_IMAGE") 228 if image: 229 urlencoded_params += "&image={}".format(quote(image, safe=":/")) 230 231 url = f"otpauth://totp/{quote(label)}?{urlencoded_params}" 232 233 return url 234 235 def _read_str_from_settings(self, key): 236 val = getattr(settings, key, None) 237 if callable(val): 238 val = val(self) 239 if isinstance(val, str) and (val != ""): 240 return val 241 return None 242 243 class Meta(Device.Meta): 244 verbose_name = _("TOTP Device") 245 verbose_name_plural = _("TOTP Devices")
23class TOTPDigits(models.TextChoices): 24 """OTP Time Digits""" 25 26 SIX = "6", _("6 digits, widely compatible") 27 EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")
OTP Time Digits
30class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage): 31 """Setup Time-based OTP authentication for the user.""" 32 33 digits = models.IntegerField(choices=TOTPDigits.choices) 34 35 @property 36 def serializer(self) -> type[BaseSerializer]: 37 from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer 38 39 return AuthenticatorTOTPStageSerializer 40 41 @property 42 def view(self) -> type[View]: 43 from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView 44 45 return AuthenticatorTOTPStageView 46 47 @property 48 def component(self) -> str: 49 return "ak-stage-authenticator-totp-form" 50 51 def ui_user_settings(self) -> UserSettingSerializer | None: 52 return UserSettingSerializer( 53 data={ 54 "title": self.friendly_name or str(self._meta.verbose_name), 55 "component": "ak-user-settings-authenticator-totp", 56 } 57 ) 58 59 def __str__(self) -> str: 60 return f"TOTP Authenticator Setup Stage {self.name}" 61 62 class Meta: 63 verbose_name = _("TOTP Authenticator Setup Stage") 64 verbose_name_plural = _("TOTP Authenticator Setup Stages")
Setup Time-based OTP authentication for the user.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
35 @property 36 def serializer(self) -> type[BaseSerializer]: 37 from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer 38 39 return AuthenticatorTOTPStageSerializer
Get serializer for this model
41 @property 42 def view(self) -> type[View]: 43 from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView 44 45 return AuthenticatorTOTPStageView
Return StageView class that implements logic for this stage
51 def ui_user_settings(self) -> UserSettingSerializer | None: 52 return UserSettingSerializer( 53 data={ 54 "title": self.friendly_name or str(self._meta.verbose_name), 55 "component": "ak-user-settings-authenticator-totp", 56 } 57 )
Entrypoint to integrate with User settings. Can either return None if no user settings are available, or a challenge.
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.
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.
Inherited Members
- authentik.flows.models.Stage
- stage_uuid
- name
- objects
- is_in_memory
- flow_set
- flowstagebinding_set
- emailstage
- endpointstage
- invitationstage
- passwordstage
- promptstage
- authenticatorstaticstage
- authenticatorduostage
- authenticatoremailstage
- authenticatorsmsstage
- authenticatorwebauthnstage
- authenticatorvalidatestage
- captchastage
- identificationstage
- authenticatortotpstage
- consentstage
- denystage
- dummystage
- redirectstage
- userdeletestage
- userloginstage
- userlogoutstage
- userwritestage
- authenticatorendpointgdtcstage
- mutualtlsstage
- sourcestage
The requested object does not exist
The query returned multiple objects when only one was expected.
Default TOTP Device key
Validate totp key
77class TOTPDevice(SerializerModel, ThrottlingMixin, Device): 78 """ 79 A generic TOTP :class:`~authentik.stages.authenticator.models.Device`. The model fields mostly 80 correspond to the arguments to :func:`authentik.stages.authenticator.oath.totp`. They all have 81 sensible defaults, including the key, which is randomly generated. 82 83 .. attribute:: key 84 85 *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20 86 random bytes) 87 88 .. attribute:: step 89 90 *PositiveSmallIntegerField*: The time step in seconds. (Default: 30) 91 92 .. attribute:: t0 93 94 *BigIntegerField*: The Unix time at which to begin counting steps. 95 (Default: 0) 96 97 .. attribute:: digits 98 99 *PositiveSmallIntegerField*: The number of digits to expect in a token 100 (6 or 8). (Default: 6) 101 102 .. attribute:: tolerance 103 104 *PositiveSmallIntegerField*: The number of time steps in the past or 105 future to allow. For example, if this is 1, we'll accept any of three 106 tokens: the current one, the previous one, and the next one. (Default: 107 1) 108 109 .. attribute:: drift 110 111 *SmallIntegerField*: The number of time steps the prover is known to 112 deviate from our clock. If :setting:`OTP_TOTP_SYNC` is ``True``, we'll 113 update this any time we match a token that is not the current one. 114 (Default: 0) 115 116 .. attribute:: last_t 117 118 *BigIntegerField*: The time step of the last verified token. To avoid 119 verifying the same token twice, this will be updated on each successful 120 verification. Only tokens at a higher time step will be verified 121 subsequently. (Default: -1) 122 123 """ 124 125 key = models.CharField( 126 max_length=80, 127 validators=[key_validator], 128 default=default_key, 129 help_text="A hex-encoded secret key of up to 40 bytes.", 130 ) 131 step = models.PositiveSmallIntegerField(default=30, help_text="The time step in seconds.") 132 t0 = models.BigIntegerField( 133 default=0, help_text="The Unix time at which to begin counting steps." 134 ) 135 digits = models.PositiveSmallIntegerField( 136 choices=[(6, 6), (8, 8)], 137 default=6, 138 help_text="The number of digits to expect in a token.", 139 ) 140 tolerance = models.PositiveSmallIntegerField( 141 default=1, help_text="The number of time steps in the past or future to allow." 142 ) 143 drift = models.SmallIntegerField( 144 default=0, 145 help_text="The number of time steps the prover is known to deviate from our clock.", 146 ) 147 last_t = models.BigIntegerField( 148 default=-1, 149 help_text=( 150 "The t value of the latest verified token. " 151 "The next token must be at a higher time step." 152 ), 153 ) 154 155 @property 156 def serializer(self) -> type[BaseSerializer]: 157 from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer 158 159 return TOTPDeviceSerializer 160 161 @property 162 def bin_key(self): 163 """ 164 The secret key as a binary string. 165 """ 166 return unhexlify(self.key.encode()) 167 168 def verify_token(self, token): 169 otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True) 170 171 verify_allowed, _ = self.verify_is_allowed() 172 if not verify_allowed: 173 return False 174 175 try: 176 token = int(token) 177 except ValueError: 178 verified = False 179 else: 180 key = self.bin_key 181 182 totp = TOTP(key, self.step, self.t0, self.digits, self.drift) 183 totp.time = time.time() 184 185 verified = totp.verify(token, self.tolerance, self.last_t + 1) 186 if verified: 187 self.last_t = totp.t() 188 if otp_totp_sync: 189 self.drift = totp.drift 190 self.throttle_reset(commit=False) 191 self.save() 192 193 if not verified: 194 self.throttle_increment(commit=True) 195 196 return verified 197 198 def get_throttle_factor(self): 199 return getattr(settings, "OTP_TOTP_THROTTLE_FACTOR", 1) 200 201 @property 202 def config_url(self): 203 """ 204 A URL for configuring Google Authenticator or similar. 205 206 See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. 207 The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available. 208 The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available. 209 210 """ 211 label = str(self.user.username) 212 params = { 213 "secret": b32encode(self.bin_key), 214 "algorithm": "SHA1", 215 "digits": self.digits, 216 "period": self.step, 217 } 218 urlencoded_params = urlencode(params) 219 220 issuer = self._read_str_from_settings("OTP_TOTP_ISSUER") 221 if issuer: 222 issuer = issuer.replace(":", "") 223 label = f"{issuer}:{label}" 224 urlencoded_params += ( 225 f"&issuer={quote(issuer)}" # encode issuer as per RFC 3986, not quote_plus 226 ) 227 228 image = self._read_str_from_settings("OTP_TOTP_IMAGE") 229 if image: 230 urlencoded_params += "&image={}".format(quote(image, safe=":/")) 231 232 url = f"otpauth://totp/{quote(label)}?{urlencoded_params}" 233 234 return url 235 236 def _read_str_from_settings(self, key): 237 val = getattr(settings, key, None) 238 if callable(val): 239 val = val(self) 240 if isinstance(val, str) and (val != ""): 241 return val 242 return None 243 244 class Meta(Device.Meta): 245 verbose_name = _("TOTP Device") 246 verbose_name_plural = _("TOTP Devices")
A generic TOTP :class:~authentik.stages.authenticator.models.Device. The model fields mostly
correspond to the arguments to :func:authentik.stages.authenticator.oath.totp. They all have
sensible defaults, including the key, which is randomly generated.
.. attribute:: key
*CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
random bytes)
.. attribute:: step
*PositiveSmallIntegerField*: The time step in seconds. (Default: 30)
.. attribute:: t0
*BigIntegerField*: The Unix time at which to begin counting steps.
(Default: 0)
.. attribute:: digits
*PositiveSmallIntegerField*: The number of digits to expect in a token
(6 or 8). (Default: 6)
.. attribute:: tolerance
*PositiveSmallIntegerField*: The number of time steps in the past or
future to allow. For example, if this is 1, we'll accept any of three
tokens: the current one, the previous one, and the next one. (Default:
1)
.. attribute:: drift
*SmallIntegerField*: The number of time steps the prover is known to
deviate from our clock. If :setting:`OTP_TOTP_SYNC` is ``True``, we'll
update this any time we match a token that is not the current one.
(Default: 0)
.. attribute:: last_t
*BigIntegerField*: The time step of the last verified token. To avoid
verifying the same token twice, this will be updated on each successful
verification. Only tokens at a higher time step will be verified
subsequently. (Default: -1)
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.
155 @property 156 def serializer(self) -> type[BaseSerializer]: 157 from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer 158 159 return TOTPDeviceSerializer
Get serializer for this model
161 @property 162 def bin_key(self): 163 """ 164 The secret key as a binary string. 165 """ 166 return unhexlify(self.key.encode())
The secret key as a binary string.
168 def verify_token(self, token): 169 otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True) 170 171 verify_allowed, _ = self.verify_is_allowed() 172 if not verify_allowed: 173 return False 174 175 try: 176 token = int(token) 177 except ValueError: 178 verified = False 179 else: 180 key = self.bin_key 181 182 totp = TOTP(key, self.step, self.t0, self.digits, self.drift) 183 totp.time = time.time() 184 185 verified = totp.verify(token, self.tolerance, self.last_t + 1) 186 if verified: 187 self.last_t = totp.t() 188 if otp_totp_sync: 189 self.drift = totp.drift 190 self.throttle_reset(commit=False) 191 self.save() 192 193 if not verified: 194 self.throttle_increment(commit=True) 195 196 return verified
Verifies a token. As a rule, the token should no longer be valid if
this returns True.
:param str token: The OTP token provided by the user. :rtype: bool
This must be implemented to return the throttle factor.
The number of seconds required between verification attempts will be
:math:c2^{n-1} where c is this factor and n is the number of
previous failures. A factor of 1 translates to delays of 1, 2, 4, 8,
etc. seconds. A factor of 0 disables the throttling.
Normally this is just a wrapper for a plugin-specific setting like
:setting:OTP_EMAIL_THROTTLE_FACTOR.
201 @property 202 def config_url(self): 203 """ 204 A URL for configuring Google Authenticator or similar. 205 206 See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. 207 The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available. 208 The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available. 209 210 """ 211 label = str(self.user.username) 212 params = { 213 "secret": b32encode(self.bin_key), 214 "algorithm": "SHA1", 215 "digits": self.digits, 216 "period": self.step, 217 } 218 urlencoded_params = urlencode(params) 219 220 issuer = self._read_str_from_settings("OTP_TOTP_ISSUER") 221 if issuer: 222 issuer = issuer.replace(":", "") 223 label = f"{issuer}:{label}" 224 urlencoded_params += ( 225 f"&issuer={quote(issuer)}" # encode issuer as per RFC 3986, not quote_plus 226 ) 227 228 image = self._read_str_from_settings("OTP_TOTP_IMAGE") 229 if image: 230 urlencoded_params += "&image={}".format(quote(image, safe=":/")) 231 232 url = f"otpauth://totp/{quote(label)}?{urlencoded_params}" 233 234 return url
A URL for configuring Google Authenticator or similar.
See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
The issuer is taken from :setting:OTP_TOTP_ISSUER, if available.
The image (for e.g. FreeOTP) is taken from :setting:OTP_TOTP_IMAGE, if available.
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.
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.
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.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
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.