Package: django-uniauth
django-uniauthdjango-uniauthis an app for allowing authentication through services commonly used by universities, such asCAS, while also permitting custom authentication schemes. This approach allows developers to leverage the user data contained within university databases, without strictly tethering themselves to those services. It also allows educational software to have a drop-in authentication solution utilizing the single-sign-on mechanisms of universities, typically CAS, to avoid requiring students to create an additional username or password.The app was designed to replace key features of the built-indjango.contrib.authpackage. Developers may simply replace the appropriate backends and URLs and let Uniauth handle authentication entirely if they wish. However, the app is also fully customizable, and components may be swapped with compatible replacements if desired.FeaturesSupports Python 2.7, 3.5+Supports Django 1.11, 2.x, 3.x, 4.xSupports using acustom User modelSupports using email addresses as the"username" fieldUsers can link multiple email addresses and use any for authenticationSupports CAS authentication and Single Sign OnMultiple CAS servers can be configured and users may use any for authenticationMajor Updates1.4.0:Added support for custom JWT token serializers1.3.1:Added support for Django 4.x and newer Python versions1.3.0:AddedJWT Support1.2.0:UniauthUserProfilemodel now backreferenced from the DjangoUsermodel viauser.uniauth_profileinstead ofuser.profile.TutorialsHow to add CAS authentication with Uniauth:linkTable of ContentsInstallationEmail SetupSettingsUsers in UniauthModelsBackendsCommandsViewsTemplate CustomizationURLsUser MigrationUsing JWT AuthenticationDemo ApplicationAcknowledgementsInstallationInstall usingpip:pip install django-uniauthAdd 'uniauth' to yourINSTALLED_APPSsetting:INSTALLED_APPS = [
    ...
    uniauth,
]Add the desired Uniauth authentication backends. For example:AUTHENTICATION_BACKENDS = [
    'uniauth.backends.LinkedEmailBackend',
    'uniauth.backends.CASBackend',
]Include theuniauthURLS in yoururls.py:urlpatterns = [
    ...
    path('accounts/', include('uniauth.urls', namespace='uniauth')),
]Lastly, add your desired institution CAS server(s). For example:python manage.py add_institution "Example Institution" https://cas.example.edu/See thecommands sectionfor more information regarding adding and removing institution CAS servers.Email SetupUniauth will send emails to users when necessary, such as to verify email addresses or for resetting passwords. During development, it may be sufficient to log these emails to the console - this is accomplished by adding the following tosettings.py:EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'On production, a real email backend should be properly set up. See the docs onsetting up an SMTP backendfor more information.SettingsUniauth uses the following settings from thedjango.contrib.authpackage:LOGIN_URL: Determines where to redirect the user for login, particularly when using the@login_requireddecorator. Defaults to/accounts/login/.PASSWORD_RESET_TIMEOUT_DAYS: Determines how long password reset and email verification links are valid after being generated. Defaults to3.The following custom settings are also used:UNIAUTH_ALLOW_SHARED_EMAILS: Whether to allow a single email address to be linked to multiple profiles. Primary email addresses (the value set in the user'semailfield) must be unique regardless. Defaults toTrue.UNIAUTH_ALLOW_STANDALONE_ACCOUNTS: Whether to allow users to log in via an Institution Account (such as via CAS) without linking it to a Uniauth profile first. If set toFalse, users will be required to create or link a profile to their Institution Accounts before being able to access views protected by the@login_requireddecorator. Defaults toTrue.UNIAUTH_FROM_EMAIL: Determines the "from" email address when Uniauth sends an email, such as for email verification or password resets. Defaults touniauth@example.com.UNIAUTH_LOGIN_DISPLAY_STANDARD: Whether the email address / password form is shown on theloginview. IfFalse, the form, "Create an Account" link, and "Forgot Password" link are hidden, and POST requests for the view will be ignored. Defaults toTrue.UNIAUTH_LOGIN_DISPLAY_CAS: Whether the option to sign in via CAS is shown on theloginview. IfTrue, there must be at least oneInstitutionin the database to log into. Also, at least one ofUNIAUTH_LOGIN_DISPLAY_STANDARDorUNIAUTH_LOGIN_DISPLAY_CASmust beTrue. Violating either of these constraints will result in anImproperlyConfiguredException. Defaults toTrue.UNIAUTH_LOGIN_REDIRECT_URL: Where to redirect the user after logging in, if no next URL is provided. Defaults to/.UNIAUTH_LOGOUT_REDIRECT_URL: Where to redirect the user after logging out, if no next URL is provided. If this setting isNone, and a next URL is not provided, the logout template is rendered instead. Defaults toNone.UNIAUTH_LOGOUT_CAS_COMPLETELY: Whether to log the user out of CAS on logout if the user originally logged in via CAS. Defaults toFalse.UNIAUTH_MAX_LINKED_EMAILS: The maximum number of emails a user can link to their profile. If this value is less than or equal to 0, there is no limit to the number of linked emails. Defaults to 20.UNIAUTH_PERFORM_RECURSIVE_MERGING: Whether to attempt to recursively merge One-to-One fields when merging users due to linking two existing accounts together. IfFalse, One-to-One fields for the user being linked in will be deleted if the primary user has a non-null value for that field. Defaults toTrue.UNIAUTH_USE_JWT_AUTH: In a REST API + UI split architecture, set toTrueto save JWTrefreshandaccesstokens in session cookie on the domain of the API. Tokens will then be retrievable by UI viaGETrequest to/jwt-tokens/. Defaults toFalse.Users in UniauthUniauth supports any custom User model, so long as the model hasusernameandemailfields. Theemailserves as the primary identifying field within Uniauth, with theusernamebeing set to an arbitrary unique value to support packages that require it. Once a user's profile has been activated, other apps are free to change theusernamewithout disrupting Uniauth's behavior.Users are created by either completing the Sign Up form, or logging in via anInstitutionAccount. In the former case, they are given a username beginning withtmp-, followed by a unique suffix, and an emptyemailfield. When the first email for a user has been verified, their profile is considered fully activated, theemailfield is set to the verified email, and theusernamefield is arbitrarily set to that email address as well, unless it is taken. In the latter case, they are given a username describing how they were authenticated, along with the institution they signed into and their ID for that institution. They will keep this username and have an emptyemailfield until they link their account to a verified Uniauth profile.Users may have multiple email addresses linked to their profile, any of which may be used for authentication (if one of theLinkedEmailUniauth backendsare used), or for password reset. The address set in the user'semailfield is considered the "primary email", and is the only one that must be unique across all users. Users may change which linked email is their primary email address at any point via thesettingspage, so long as that primary email is not taken by another user.Users may also have multipleInstitutionAccountslinked to their profile. These represent alternative ways of logging in, other than the standard username/email + password form. For example, if a University offers authentication via CAS, a user may link their CAS username for that university to their Uniauth profile, so that logging in with CAS authenticates them as the proper user.ModelsUniauth has the following models:UserProfile:This model is automatically attached to each User upon creation, and extends the User model with the extra data Uniauth requires. The other Uniauth models all interact with theUserProfilemodel rather than the User model directly. Accessible viauser.uniauth_profile.get_display_id: This method returns a more display-friendly ID for the user, using their username. If the User was created via CAS authentication, it will return their username without the institution prefix (so a User with username "cas-exampleinst-id123" would return "id123"). If their username is an email address, it will return everything before the "@" symbol (so "johndoe@example.com" would become "johndoe"). Otherwise the username is returned unmodified. These generated IDs are not guaranteed to be unique.LinkedEmail:Represents an email address linked to a User's account. Accessible viauser.uniauth_profile.linked_emails.Institution:Represents an organization possesing an authentication server that can be logged into. You will need to add an Institution for each CAS server you wish to support. Theadd_institutionandremove_institutioncommands are provided to help with this.InstitutionAccount:Represents an account a User holds with a particular Institution. Accessible viauser.uniauth_profile.accounts.BackendsTo use Uniauth as intended, either theLinkedEmailBackendor theUsernameOrLinkedEmailBackendshould be included in yourAUTHENTICATION_BACKENDSsetting, along with the backends for any other authentication methods you wish to support.CASBackend:TheCASBackendis inspired from thedjango-cas-ng backendof the same name, and is largely a streamlined version of that class, modified to support multiple CAS servers. This backend'sauthenticatemethod accepts aninstitution, aticket, and aserviceURL to redirect to on successful authentication, and attempts to verify that ticket with the institution's CAS server.If verification succeeds, it looks for anInstitutionAccountmatching that CAS username, and returns the user for the associated profile. If it succeeds, but there is no suchInstitutionAccount, a temporary user is created, and the client will eventually be prompted to link this username to an existing Uniauth profile, or create one. If verification fails, authentication fails as well.LinkedEmailBackend:This backend'sauthenticatemethod accepts an email and password as keyword arguments, and checks the password against all users with that email linked to their account. If anemailis not explicitly provided, a few other common field names (such asemail_addressandusername) are checked and used if found.Note:Since the default Django admin page uses same Authentication Backends as the rest of the site, replacing the defaultModelBackendwith this one will result in usernames no longer being recognized on the admin login screen. You will need to log in with a superuser's email address and password, or use the belowUsernameOrLinkedEmailBackendinstead.UsernameOrLinkedEmailBackend:Identical to the above class, except the providedemailargument is also checked against each user'susername.CommandsUniauth provides the following management commands:add_institution <name> <cas_server_url>: Adds anInstitutionwith the provided name and CAS server URL to the database. Thenamewill be the text displayed in the CAS server dropdown on the Login page, andcas_server_urlmust point to the root URL of a CAS protocol compliant service. The command will return the institution's slug created from the provided name; this slug must be used when referring to the institution in other commands (such asremove_institution).Example Usage:python manage.py add_institution "Example Inst" "https://www.example.com/cas/"You may add the--update-existingoption to update the CAS server URL of an existing institution with that name, or create one if it does not exist.remove_institution <slug>: Removes theInstitutionwith the provided slug from the database. This action removes anyInstitutionAccountsfor that instiutiton in the process.migrate_cas <slug>: Migrates a project originally using CAS for authentication to using Uniauth. See theUser Migrationsection for more information.migrate_custom: Migrates a project originally using custom User authentication to using Uniauth. See theUser Migrationsection for more information.flush_tmp_users [days]: Deletes temporary users more than the specified number of days old from the database. The default number of days is 1.ViewsThe five views you will likely care about the most arelogin,logout,signup,password-reset, andsettings:/login/: Displays a page allowing users to log in by entering a username/email and password, or via a supported backend, such as CAS. Also displays links for creating an account directly, and for resetting passwords./logout/: Logs out the user. The behavior and redirect location of the log out is determined by the app's settings./signup/: Prompts user for a primary email address, and a password, then sends a verification email to that address to activate the account./password-reset/: Prompts user for an email address, then sends an email to that address containing a link for resetting the password. If no users have the entered email address linked to their account, no email is sent. If multiple users have that address linked, an email is sent for each potential user./settings/: Allows users to perform account related actions, such as link more email addresses, choose the primary email address, link more Institution Accounts, or change their password./jwt-tokens/: In REST API + UI split, allows UI to pop JWT tokens from session cookie on API domain via methodGET. Returns404status if refresh and access tokens are not set.The remaining views are used internally by Uniauth, and should not be linked to from outside the app:/cas-login/: If a user chooses to log in via CAS, this view is called with the institution the user wishes to log into as an argument. The view will first redirect to the institution's CAS server and attempt to get a ticket, then return to the original page and attempt to authenticate with that ticket, via theCASBackend./link-to-account/: If the user is logged into anInstitutionAccountnot yet linked to a Uniauth profile, this view offers them the choice between linking it to an existing profile, or creating a new one, and linking it to that upon activation./link-from-account/: If the user is logged into an activated Uniauth profile, this view gives them the opportunity to log into an institution via a supported backend, then link thatInstitutionAccountto the current profile./verify-token/: Intermediate page used during the email verification process. Verifies the token contained within the link sent to the email address./password-reset-*/: Intermediate pages used during the password reset process. Are nearly identical to thebuilt-in password reset viewsprovided by thedjango.contrib.authpackage.Uniauth also implements its own version of the@login_requireddecorator, which ensure the user is logged in with an activated Uniauth profile before accessing the view. It may be used identically to thebuilt-in@login_requireddecorator, and should be added to your own views in place of the default version.Template CustomizationThe presentation of the views can be easily changed by overriding the appropriate template(s). For example, to add your own stylesheet to the Uniauth templates, create auniauthfolder in yourtemplatesdirectory, and add abase-site.htmlfile to override the default one like so:{% extends "uniauth/base.html" %}

{% load static from staticfiles %}

{% block shared-head %}
<link rel="shortcut icon" href="{% static 'uniauth/img/favicon.ico' %}"/>
<link href="{% static 'path/to/custom-style.css' %}" rel="stylesheet" type="text/css"/>
{% endblock %}

{% block body %}
<div id="wrapper">
    <div id="page-wrapper" class="lavender-bg">
        <div id="content-wrapper">
            <div id="top-background"></div>
            {% block content %}
            {% endblock %}
        </div>
    </div>
</div>
{% endblock %}More specific changes can be made by overriding the appropriate template.URLsTo add the Uniauth views to your app, you must add an entry to yoururlpatternswhich includes them with the namespace "uniauth". For example:path('accounts/', include('uniauth.urls', namespace='uniauth'))Including theuniauth.urlsmodule will add all of Uniauth's views to your app. However, if you only wish to use CAS authentication, you may choose to include theuniauth.urls.cas_onlymodule instead, which will only expose thelogin,cas-login, andlogoutviews.URL ParametersAll views except/settings/persist URL parameters to their final destination. This means you can add a query string to theloginURL, and have it apply to theUNIAUTH_LOGIN_REDIRECT_URLpage, for example.The only URL parameter that is not preserved is thenextvariable, which indicates the desired location to redirect to after business in the current view is completed. This variable is consumed upon successful redirection to that location, and can be used to dynamically control how the app behaves after visiting a view.User MigrationIf you wish to use Uniauth with a project that already has users, aUserProfile(and, if applicable,LinkedEmailorInstitutionAccount) will need to be created for each existing user. You may use one of the provided commands to assist with this, provided your project meets one of the following conditions:If you were previously using CAS for authentication, and the username for each user matches the CAS ID (as would be the case if you were using a package likedjango-cas-ng), you should firstadd an Institutionfor the CAS server you were using, then use themigrate_cascommand with the slug of the created Institution to peform the migration. AUserProfilewill be created for all users, and the usernames of all Users will be changed to conform to Uniauth's expectations (tocas-<institution_slug>-<original_username>). To get the original username (without the CAS institution prefix), use theget_display_idmethod provided by theUserProfilemodel.If you were previously using custom user authentication (as in, Users would sign up with a username / email address and password), you may use themigrate_customcommand to migrate the users. AUserProfilewill be created for each migrated user, and a verifiedLinkedEmailwill also be created for all users with a non-blankemailfield. Note that any users lacking a username / email or password will not be migrated. Also note that if theLinkedEmailBackendis used, users that don't have aLinkedEmailcreated will not be able to log in until one is linked.If your project does not fit either of these conditions, you will need to manually migrate the users as appropiate. Please create aUserProfilefor each user, andLinkedEmailsorInstitutionAccountsas appropiate.Using JWT AuthenticationIf you wish to use django-uniauth with JWT authentication (API + UI split), you will need to enableSessionMiddlewarein addition to the following settings:UNIAUTH_USE_JWT_AUTHexplicitly set toTrue.UNIAUTH_LOGIN_REDIRECT_URLset to any route of your choice to your UI (ex:http://your-ui-domain.com/)UNIAUTH_LOGOUT_REDIRECT_URLset to any route of your choice to your UI (ex:http://your-ui-domain.com/)Additionally, you may need thedjango-cors-headerspackage to allow your UI to make requests with your API. From this package, you will needCORS_ALLOWED_ORIGINS=["http://you-ui-domain.com/"]CORS_ALLOW_CREDENTIALS=TrueOn the UI, you can link your login/signup buttons to the respective django-uniauth views. Upon logging in, Uniauth will save your JWT tokens in a session cookie on the API's domain. To retrieve and save these tokens, make aGETrequest withcredentials: "include"in the request headers.If you have created aTokenObtainPairSerializer subclassfor customizing token claims, set theTOKEN_OBTAIN_SERIALIZERsimplejwt setting accordingly, and Uniauth will use the custom serializer for its JWT tokens as well.Please refer todjango-rest-framework-simplejwtfor more information on customizing tokens (i.e. token expiration) and more.Demo ApplicationThe source repository contains ademo_appdirectory which demonstrates how to setup a simple Django app to use Uniauth. This app has no functionality, and exists solely to show off the installableuniauthapp. A quick-start guide for integrating Uniauth can be foundhere.AcknowledgementsSpecial thank you toJérémie Lumbrosofor his guidance in developing this package.