Pluggable authentication backends¶
Plugins can supply additional authentication backends. This is mainly useful in self-hosted installations and allows you to use company-wide login mechanisms such as LDAP or OAuth for accessing pretix’ backend.
Every authentication backend contains an implementation of the interface defined in pretix.base.auth.BaseAuthBackend
(see below). Note that pretix authentication backends work differently than plain Django authentication backends.
Basically, three pre-defined flows are supported:
Authentication mechanisms that rely on a set of input parameters, e.g. a username and a password. These can be implemented by supplying the
login_form_fields
property and aform_authenticate
method.Authentication mechanisms that rely on external sessions, e.g. a cookie or a proxy HTTP header. These can be implemented by supplying a
request_authenticate
method.Authentication mechanisms that rely on redirection, e.g. to an OAuth provider. These can be implemented by supplying a
authentication_url
method and implementing a custom return view.
For security reasons, authentication backends are not automatically discovered through a signal. Instead, they must
explicitly be set through the auth_backends
directive in the pretix.cfg
configuration file.
In each of these methods (form_authenticate
, request_authenticate
, or your custom view) you are supposed to
use User.objects.get_or_create_for_backend
to get a pretix.base.models.User
object from the database
or create a new one.
There are a few rules you need to follow:
You MUST have some kind of identifier for a user that is globally unique and SHOULD never change, even if the user’s name or email address changes. This could e.g. be the ID of the user in an external database. The identifier must not be longer than 190 characters. If you worry your backend might generated longer identifiers, consider using a hash function to trim them to a constant length.
You SHOULD not allow users created by other authentication backends to log in through your code, and you MUST only create, modify or return users with
auth_backend
set to your backend.Every user object MUST have an email address. Email addresses are globally unique. If the email address is already registered to a user who signs in through a different backend, you SHOULD refuse the login.
User.objects.get_or_create_for_backend
will follow these rules for you automatically. It works like this:
- class pretix.base.models.auth.UserManager(*args, **kwargs)¶
This is the user manager for our custom user model. See the User model documentation to see what’s so special about our user model.
- get_or_create_for_backend(backend, identifier, email, set_always, set_on_creation)¶
This method should be used by third-party authentication backends to log in a user. It either returns an already existing user or creates a new user.
In pretix 4.7 and earlier, email addresses were the only property to identify a user with. Starting with pretix 4.8, backends SHOULD instead use a unique, immutable identifier based on their backend data store to allow for changing email addresses.
This method transparently handles the conversion of old user accounts and adds the backend identifier to their database record.
This method will never return users managed by a different authentication backend. If you try to create an account with an email address already blocked by a different authentication backend,
EmailAddressTakenError
will be raised. In this case, you should display a message to the user.- Parameters:
backend – The identifier attribute of the authentication backend
identifier – The unique, immutable identifier of this user, max. 190 characters
email – The user’s email address
set_always – A dictionary of fields to update on the user model on every login
set_on_creation – A dictionary of fields to set on the user model if it’s newly created
- Returns:
A User instance.
The backend interface¶
- class pretix.base.auth.BaseAuthBackend¶
The central object of each backend is the subclass of
BaseAuthBackend
.- BaseAuthBackend.identifier¶
A short and unique identifier for this authentication backend. This should only contain lowercase letters and in most cases will be the same as your package name.
This is an abstract attribute, you must override this!
- BaseAuthBackend.verbose_name¶
A human-readable name of this authentication backend.
This is an abstract attribute, you must override this!
- BaseAuthBackend.login_form_fields¶
This property may return form fields that the user needs to fill in to log in.
- BaseAuthBackend.visible¶
Whether or not this backend can be selected by users actively. Set this to
False
if you only implementrequest_authenticate
.
- BaseAuthBackend.form_authenticate(request, form_data)¶
This method will be called after the user filled in the login form.
request
will contain the current request andform_data
the input for the form fields defined inlogin_form_fields
. You are expected to either return aUser
object (if login was successful) orNone
.You are expected to either return a
User
object (if login was successful) orNone
. You should obtain this user object usingUser.objects.get_or_create_for_backend
.
- BaseAuthBackend.request_authenticate(request)¶
This method will be called when the user opens the login form. If the user already has a valid session according to your login mechanism, for example a cookie set by a different system or HTTP header set by a reverse proxy, you can directly return a
User
object that will be logged in.request
will contain the current request.You are expected to either return a
User
object (if login was successful) orNone
. You should obtain this user object usingUser.objects.get_or_create_for_backend
.
- BaseAuthBackend.authentication_url(request)¶
This method will be called to populate the URL for your authentication method’s tab on the login page. For example, if your method works through OAuth, you could return the URL of the OAuth authorization URL the user needs to visit.
If you return
None
(the default), the link will point to a page that shows the form defined bylogin_form_fields
.
Logging users in¶
If you return a user from form_authenticate
or request_authenticate
, the system will handle everything else
for you correctly. However, if you use a redirection method and build a custom view to verify the login, we strongly
recommend that you use the following utility method to correctly set session values and enforce two-factor
authentication (if activated):
- pretix.control.views.auth.process_login(request, user, keep_logged_in)¶
This method allows you to return a response to a successful log-in. This will set all session values correctly and redirect to either the URL specified in the
next
parameter, or the 2FA login screen, or the dashboard.- Returns:
This method returns a
HttpResponse
.
A custom view that is called after a redirect from an external identity provider could look like this:
1from django.contrib import messages
2from django.shortcuts import redirect
3from django.urls import reverse
4
5from pretix.base.models import User
6from pretix.base.models.auth import EmailAddressTakenError
7from pretix.control.views.auth import process_login
8
9
10def return_view(request):
11 # Verify validity of login with the external provider's API
12 api_response = my_verify_login_function(
13 code=request.GET.get('code')
14 )
15
16 try:
17 u = User.objects.get_or_create_for_backend(
18 'my_backend_name',
19 api_response['userid'],
20 api_response['email'],
21 set_always={
22 'fullname': '{} {}'.format(
23 api_response.get('given_name', ''),
24 api_response.get('family_name', ''),
25 ),
26 },
27 set_on_creation={
28 'locale': api_response.get('locale').lower()[:2],
29 'timezone': api_response.get('zoneinfo', 'UTC'),
30 }
31 )
32 except EmailAddressTakenError:
33 messages.error(
34 request, _('We cannot create your user account as a user account in this system '
35 'already exists with the same email address.')
36 )
37 return redirect(reverse('control:auth.login'))
38 else:
39 return process_login(request, u, keep_logged_in=False)