Package: greendeploy-django-magiclink
Django MagicLinkPasswordless Authentication for Django with Magic Links.This package was created with a focus onease of setup,securityand testing (coverage is currently at 100%). The idea is to use sane defaults to quickly create secure single-use token authentication for Django.Installpipinstalldjango-magiclinkSetupThe setup of the app is simple but has a few steps and a few templates that need overriding.Install the appConfigure the appadding urls and settings. There are also a number ofadditional configuration settingsSet up the login pageby overriding the login page templateOverride the login sent page HTMLCustomise the login failed pageSet up the magic link email(optional) by setting the email logo and colours. It's also possible to override the email templatesCreate a signup page(optional) depending on your settings configurationBasic login flowThe user signs up via the sign up page (This can be skipped ifMAGICLINK_REQUIRE_SIGNUP = False)They enter their email on the login page to request a magic linkA magic link is sent to users email addressThe user is redirected to a login sent pageThe user clicks on the magic link in their emailThe user is logged in and redirectedIf you want to create a different passwordless login flow see theManual usagesectionConfigurationAdd to theurlpatternsinurls.py:urlpatterns=[...path('auth/',include('magiclink.urls',namespace='magiclink')),...]Addmagiclinkto yourINSTALLED_APPS:INSTALLED_APPS=(...'magiclink',...)AUTHENTICATION_BACKENDS=('magiclink.backends.MagicLinkBackend',...'django.contrib.auth.backends.ModelBackend',)Note: MagicLinkBackend should be placed at the top of AUTHENTICATION_BACKENDSto ensure it is used as the primary login backend.Add the following settings to yoursettings.py(you will need to replace the template names in the below steps):# Set Djangos login URL to the magiclink login pageLOGIN_URL='magiclink:login'MAGICLINK_LOGIN_TEMPLATE_NAME='magiclink/login.html'MAGICLINK_LOGIN_SENT_TEMPLATE_NAME='magiclink/login_sent.html'MAGICLINK_LOGIN_FAILED_TEMPLATE_NAME='magiclink/login_failed.html'# Optional:# If this setting is set to False a user account will be created the first# time a user requests a login link.MAGICLINK_REQUIRE_SIGNUP=TrueMAGICLINK_SIGNUP_TEMPLATE_NAME='magiclink/signup.html'Seeadditional configuration settingsfor all of the different available settings.Once the app has been added toINSTALLED_APPSyou must run the migrations formagiclinkpythonmanage.pymigratemagiclinkLogin pageEach login page will need different HTML so you need to set theMAGICLINK_LOGIN_TEMPLATE_NAMEsetting to a template of your own. When overriding this template please ensure the following code is included:<formaction="{% url 'magiclink:login' %}{% if request.GET.next %}?next={{ request.GET.next }}{% endif %}"method="post">{% csrf_token %}
    {{ login_form }}<buttontype='submit'>Send login email</button></form>See the login docs if you want to create your own login viewLogin sent pageAfter the user has requested a magic link, they will be redirected to a success page. The HTML for this page can be overridden using the settingMAGICLINK_LOGIN_SENT_TEMPLATE_NAME. It is advised you return a simple message telling the user to check their email:<h1>Check your email</h1><p>We have sent you a magic link to your email address</p><p>Please click the link to be logged in automatically</p>Login failed pageIf the user tries to use an invalid magic token they will be shown a custom error page. To override the HTML for this page you can set theMAGICLINK_LOGIN_FAILED_TEMPLATE_NAMEsetting. If you would like to return a 404 page you can set this setting to a empty string (or any falsy value). If you would like to redirect to another page (say a custom front-end) you can use theMAGICLINK_LOGIN_FAILED_REDIRECTsetting.The reasons for the login failing is passed through as the context variable{{ login_error }}To help tailor the error page and explain the possible reasons the user could not login the following context variables are provided:{{ login_error }}- The reason the login failed (raised byMagicLink.validate()){{ one_token_per_user }}- The value of theMAGICLINK_ONE_TOKEN_PER_USERsetting{{ require_same_browser }}- The value of theMAGICLINK_REQUIRE_SAME_BROWSERsetting{{ require_same_ip }}- The value of theMAGICLINK_REQUIRE_SAME_IPsetting{{ allow_superuser_login }}- The value of theMAGICLINK_ALLOW_SUPERUSER_LOGINsetting{{ allow_staff_login }}- The value of theMAGICLINK_ALLOW_STAFF_LOGINsettingFor an example of this page see thedefault login failed templateMagic link emailThe login email which includes the magic link needs to be configured. By default, a simple HTML template is used which can be adapted to your own branding using theMAGICLINK_EMAIL_STYLESsetting, or you can override the template (see below)ThisMAGICLINK_EMAIL_STYLESsetting should be a dict with the following key values:MAGICLINK_EMAIL_STYLES={'logo_url':'https://example.com/logo.png','background-colour':'#ffffff','main-text-color':'#000000','button-background-color':'#0078be','button-text-color':'#ffffff',}Note: The logo URL must be a full URL. For email client support you should use either a jpeg or png.If this email template is not to your liking you can override the email templates (one for text and one for html). To do so you need to override theMAGICLINK_EMAIL_TEMPLATE_NAME_TEXTandMAGICLINK_EMAIL_TEMPLATE_NAME_HTMLsettings.  If you override these templates the following context variables are available:{{ subject }}- The subject of the email "Your login magic link"{{ magiclink }}- The magic link URL{{ user }}- The full user object{{ expiry }}- Datetime for when the magiclink expires{{ ip_address }}- The IP address of the person who requested the magic link{{ created }}- Datetime of when the magic link was created{{ require_same_ip }}- The value ofMAGICLINK_REQUIRE_SAME_IP{{ require_same_browser }}- The value ofMAGICLINK_REQUIRE_SAME_BROWSER{{ token_uses }}- The value ofMAGICLINK_TOKEN_USESSignup pageIf you want users to have to signup before being able to log in you will want to override the signup page template using theMAGICLINK_SIGNUP_TEMPLATE_NAMEsetting. This is needed whenMAGICLINK_REQUIRE_SIGNUP = True. On successful signup the user will be sent a login email with a magic link.When overriding this template please ensure the following content is included:<formaction="{% url 'magiclink:signup' %}"method="post">{% csrf_token %}
    {{ SignupForm }}<buttontype='submit'>Signup</button></form><p>Already have an account?<ahref='{% url 'magiclink:login'%}'>Log in here</a></p>There are several forms made avalible in the context on this page depending on what information you want to collect:SignupFormEmailOnly- Only includes anemailfieldSignupForm- IncludesnameandemailfieldsSignupFormWithUsername- IncludesusernameandemailfieldsSignupFormFull- Includesusername,nameandemailfieldsLike the login for the sign up flow can be overridden if you require more information from the user on signup. See the login/setup docs for more details.Configuration settingsBelow are the different settings that can be overridden. To do so place the setting into yoursettings.py.Note: Each of the url / redirect settings can either be a URL or url name# Override the login page template. See 'Login page' in the Setup sectionMAGICLINK_LOGIN_TEMPLATE_NAME='myapp/login.html'# Override the login page template. See 'Login sent page' in the Setup sectionMAGICLINK_LOGIN_SENT_TEMPLATE_NAME='myapp/login_sent.html'# Override the template that shows when the user tries to login with a# magic link that is not valid. See 'Login failed page' in the Setup sectionMAGICLINK_LOGIN_FAILED_TEMPLATE_NAME='magiclink/login_failed.html'# If a login failed redirect is specified the user will be redirected to this# URL instead of being shown the LOGIN_FAILED_TEMPLATEMAGICLINK_LOGIN_FAILED_REDIRECT=''# If this setting is set to False a user account will be created the first time# a user requests a login link.MAGICLINK_REQUIRE_SIGNUP=True# Override the login page template. See 'Login sent page' in the Setup sectionMAGICLINK_SIGNUP_TEMPLATE_NAME='myapp/signup.html'# Set Djangos login redirect URL to be used once the user opens the magic link# This will be used whenever a ?next parameter is not set on loginLOGIN_REDIRECT_URL='/accounts/profile/'# If a new user is created via the signup page use this setting to send them to# a different url than LOGIN_REDIRECT_URL when clicking the magic link# This will fall back to LOGIN_REDIRECT_URLMAGICLINK_SIGNUP_LOGIN_REDIRECT='/welcome/'# Change the url a user is redirect to after requesting a magic linkMAGICLINK_LOGIN_SENT_REDIRECT='magiclink:login_sent'# Ensure the branding of the login email is correct. This setting is not needed# if you override the `login_email.html` templateMAGICLINK_EMAIL_STYLES={'logo_url':'','background-colour':'#ffffff','main-text-color':'#000000','button-background-color':'#0078be','button-text-color':'#ffffff',}# If you want to use your own email templates you can override the text and# html templates used with:MAGICLINK_EMAIL_TEMPLATE_NAME_TEXT='myapp/login_email.text'MAGICLINK_EMAIL_TEMPLATE_NAME_HTML='myapp/login_email.html'# How long a magic link is valid for before returning an errorMAGICLINK_AUTH_TIMEOUT=300# In second - Default is 5 minutes# Email address is not case sensitive. If this setting is set to True all# emails addresses will be set to lowercase before any checks are run against itMAGICLINK_IGNORE_EMAIL_CASE=True# When creating a user assign their email as the username (if the User model# has a username field)MAGICLINK_EMAIL_AS_USERNAME=True# Allow superusers to login via a magic linkMAGICLINK_ALLOW_SUPERUSER_LOGIN=True# Allow staff users to login via a magic linkMAGICLINK_ALLOW_STAFF_LOGIN=True# Ignore the Django user model's is_active flag for login requestsMAGICLINK_IGNORE_IS_ACTIVE_FLAG=True# Override the default magic link length# Warning: Overriding this setting has security implications, shorter tokens# are much more susceptible to brute force attacks*MAGICLINK_TOKEN_LENGTH=50# Require the user email to be included in the verification link# Warning: If this is set to false tokens are more vulnerable to brute forceMAGICLINK_VERIFY_INCLUDE_EMAIL=True# Ensure the user who clicked magic link used the same browser as the# initial login request.# Note: This can cause issues on devices where the default browser is# different from the browser being used by the user such as on iOS)MAGICLINK_REQUIRE_SAME_BROWSER=True# Ensure the user who clicked magic link has the same IP address as the# initial login request.MAGICLINK_REQUIRE_SAME_IP=True# Remove the last 8-bit octet of a clients IP address.# Note: This has no effect if MAGICLINK_REQUIRE_SAME_IP as no IP address# is storedMAGICLINK_ANONYMIZE_IP=True# The number of times a login token can be used before being disabledMAGICLINK_TOKEN_USES=1# How often a user can request a new login token (basic rate limiting).MAGICLINK_LOGIN_REQUEST_TIME_LIMIT=30# In seconds# Disable all other tokens for a user when a new token is requestedMAGICLINK_ONE_TOKEN_PER_USER=True# Include basic anti spam form fields to help stop bots. False by default# Note: IF you use the default forms you will need to add CSS to your# page / stylesheet to hide the labels for the anti spam fields.# See the login.html or signup.html for an exampleMAGICLINK_ANTISPAM_FORMS=False# The shortest time a user can fill out each field and submit a form without# being considered a bot. The time is per field and defaults to 1 second.# This means if the form has 3 fields and the user will need to make more than# 3 seconds to fill out a form.MAGICLINK_ANTISPAM_FIELD_TIME=1# Override the login verify address. You must inherit from Magiclink LoginVerify# view. See Manual usage for more detailsMAGICLINK_LOGIN_VERIFY_URL='magiclink:login_verify'# If an email address has been added to the unsubscribe table but is also# assocaited with a Django user, should a login email be sentMAGICLINK_IGNORE_UNSUBSCRIBE_IF_USER=FalseMagic Link cleanupEach Magic Link is a seperate row in the database. To help give the user a better warning as to why their login was not successful, magic links are not cleared even once they have expired or have been disabled.To clear old disabled magic links as well as magic links which expired over 1 week ago, you can use themagiclink_clear_loginsmanagement commandpython manage.py magiclink_clear_loginsSecurityUsing magic links can be dangerous as poorly implemented login links can be brute-forced and emails can be forwarded by accident. There are several security measures used to mitigate these risks:The one-time password issued will be valid for 5 minutes before it expiresThe user's email is specified alongside login tokens to stop URLs being brute-forcedEach login token will be at least 20 digitsThe initial request and its response must take place from the same IP addressThe initial request and its response must take place in the same browserEach one-time link can only be used onceOnly the last one-time link issued will be accepted. Once the latest one is issued, any others are invalidated.Note: Each of the above settings can be overridden / changed when configuring django-magiclinkUnsubscribe / stopping email spamSadly bots like to go around the internet and fill out any forms they can with random email addresses that don't belong to them. Because of this, if an email is added to theMagicLinkUnsubscribemodel they will no longer receive a login or welcome email, even if the email has a user associated with it. This behaviour can be changed for existing users using theMAGICLINK_IGNORE_UNSUBSCRIBE_IF_USERsetting.Adding a user to the unsubscribe model is done using the normal Django ORM create methodfrommagiclink.modelsimportMagicLinkUnsubscribeMagicLinkUnsubscribe.objects.create(email='test@example.com')If you are using the Django Magiclink login or signup functionality, the unsubscribe check happens during form validation. This means a new user will never be created if their email address has already been added to theMagicLinkUnsubscribelist.Manual usageCreating magiclinksdjango-magiclink uses a model to help create, send and validate magic links. Acreate_magiclinkhelper function can be used easily create a MagicLink using the correct settings:frommagiclink.helpersimportcreate_magiclink# Returns newly created from magiclink.models.MagicLink instancemagiclink=create_magiclink(email,request,redirect_url='')# Generates the magic link url and sends it in an emailmagiclink.send(request)# If you want to build the magic link from the model instance but don't want to#  send the email you can you can use:magic_link_url=magiclink.generate_url(request)Custom Login verify flowIt is also possible to override the login verify flow to run your own code once the user has successfully logged in instead of a simple redirect. To do this you will need to create a new view which inherits themagiclink.views.LoginVerifyview and overrides thelogin_complete_actionmethod.The below example will redirect the user to a different page depending on superuser / staff status:Your ownviews.pyfrommagiclink.viewsimportLoginVerifyclassCustomLoginVerify(LoginVerify):deflogin_complete_action(self):ifself.request.user.is_superuser:url=reverse('superuser_page')elifself.request.user.is_staff:url=reverse('staff_page')else:url=reverse('normal_page')returnHttpResponseRedirect(url)Your ownurls.pyurlpatterns=[...path('custom-login-verify/',CustomLoginVerify.as_view(),name='custom_login_verify'),...]settings.py...MAGICLINK_LOGIN_VERIFY_URL='custom_login_verify'...UpgradingA new migration was added to version1.2.0. If you upgrade to1.2.0or above from a previous version please ensure you migratepythonmanage.pymigratemagiclink