<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AM.HP Blog]]></title><description><![CDATA[AM.HP Blog]]></description><link>https://blog.am-hp.ir</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 04:09:38 GMT</lastBuildDate><atom:link href="https://blog.am-hp.ir/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Email Authentication System]]></title><description><![CDATA[Unfortunately django hasn't provided a builtin email authentication back end, this is while nowadays most websites are using this kinda systems as their user validation methods and now we have to build it by our selves
in this article will represent ...]]></description><link>https://blog.am-hp.ir/email-authentication-system</link><guid isPermaLink="true">https://blog.am-hp.ir/email-authentication-system</guid><dc:creator><![CDATA[Amir Mohammad Hamidi Pour]]></dc:creator><pubDate>Tue, 06 Sep 2022 16:58:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662584536629/9bgbMZT7e.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Unfortunately django hasn't provided a builtin email authentication back end, this is while nowadays most websites are using this kinda systems as their user validation methods and now we have to build it by our selves</p>
<p>in this article will represent how to create an email authentication system for a django application, you can find the project source in my <a target="_blank" href="https://github.com/amir-mohammad-HP/django4-SampleCode/tree/main/Email%20Authentication">github</a>.</p>
<p>NOTE : presumed that you will start the app by a custom user model(because we rewrite User email) and if you hadn't, make sure each USER use a unique email address.</p>
<h3 id="heading-create-a-project">Create a project</h3>
<p>we call project folder to <strong>email_authetication</strong></p>
<pre><code class="lang-batch">django-admin startproject email_authentication .
</code></pre>
<h3 id="heading-start-app">start app</h3>
<p>let's call the app <strong>authenticate</strong></p>
<pre><code class="lang-batch">python3 manage.py startapp authenticate
</code></pre>
<p>NOTE : do not makemigrations untill we create a custom User model</p>
<p>now add the app to <strong>INSTALLED_APPS</strong> in <em>./email_authetication/settings.py</em></p>
<h2 id="heading-built-custom-user-model">Built Custom User Model :</h2>
<p>let's create a custom User model, it is helpful to always create a User model even if you won't need it at the time.</p>
<p>but now we need to rewrite the email_field to make sure we won't get two users with the same email address </p>
<p><strong>1-</strong> in <em>./authenticate/models.py</em> </p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib.auth.models <span class="hljs-keyword">import</span> AbstractUser
<span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models
<span class="hljs-keyword">from</span> django.utils.translation <span class="hljs-keyword">import</span> gettext_lazy <span class="hljs-keyword">as</span> _

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>(<span class="hljs-params">AbstractUser</span>):</span>
    email = models.EmailField(
        _(<span class="hljs-string">"email address"</span>),
        unique=<span class="hljs-literal">True</span>,
        blank=<span class="hljs-literal">True</span>,
        error_messages={
            <span class="hljs-string">"unique"</span>: _(<span class="hljs-string">"A user with that email already exists."</span>),
        },
        )
</code></pre>
<p><strong>2-</strong> in <em>./authenticate/admin.py</em></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.contrib.auth.admin <span class="hljs-keyword">import</span> UserAdmin
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> User

admin.site.register(User, UserAdmin)
</code></pre>
<p><strong>3-</strong> in <em>./email_authentication/settings.py</em> we need to tell django to use our custom User model</p>
<pre><code class="lang-python">AUTH_USER_MODEL = <span class="hljs-string">'authenticate.User'</span>
</code></pre>
<p>django will look to <em>authenticate</em> app then in <em>models</em> look for <em>User</em> model.</p>
<p>now ... let's do the first <em>makemigrations</em> and <em>migrate</em> the changes</p>
<pre><code class="lang-batch">python3 manage.py makemigrations
python3 manage.py migrate
</code></pre>
<p>congratulations, now we built our custom model ... for making sure we use the right User model, you can have access to User model by the following path:</p>
<pre><code class="lang-python"><span class="hljs-comment"># not recommended</span>
<span class="hljs-keyword">from</span> authentication.models <span class="hljs-keyword">import</span> User

<span class="hljs-comment"># recommended</span>
<span class="hljs-keyword">from</span> django.contrib.auth <span class="hljs-keyword">import</span> get_user_model <span class="hljs-comment"># a function that return the current user model</span>
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings <span class="hljs-comment"># get settings.AUTH_USER_MODEL</span>
</code></pre>
<p>the two recommended ways are kinda the same </p>
<h2 id="heading-build-authentication-backend"><p id="built_authentication"> Build Authentication Backend </p></h2>
<p>we need to create a new authentication backend to tell django to also use this backend to authentify users</p>
<p>create a new file and name it <em>./authenticate/backends.py</em></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib.auth <span class="hljs-keyword">import</span> get_user_model
<span class="hljs-keyword">from</span> django.contrib.auth.backends <span class="hljs-keyword">import</span> ModelBackend

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EmailAuthentication</span>(<span class="hljs-params">ModelBackend</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">authenticate</span>(<span class="hljs-params">self, request, email=None, password=None, **kwargs</span>):</span>
        UserModel = get_user_model()
        <span class="hljs-keyword">try</span>:
            user = UserModel.objects.get(email=email)
        <span class="hljs-keyword">except</span> UserModel.DoesNotExist:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
        <span class="hljs-keyword">except</span> UserModel.MultipleObjectsReturned:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span> <span class="hljs-comment"># in case multiple users with the same email exist none of them will authentify</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">if</span> user.check_password(password):
                <span class="hljs-keyword">return</span> user
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
</code></pre>
<p>in <em>./email_authentication/settings.py</em></p>
<pre><code class="lang-python">AUTHENTICATION_BACKENDS = [
    <span class="hljs-string">'django.contrib.auth.backends.ModelBackend'</span>, <span class="hljs-comment"># keep username and password authentication</span>
    <span class="hljs-string">'authenticate.backends.EmailAuthentication'</span>, <span class="hljs-comment"># add email and password authentication</span>
    ]
</code></pre>
<p>add django and our custom backends to settings to tell Django use this backend too, then
our email authentication system is complete</p>
<p>now you can authenticate the user in both <em>username and passsword</em> or <em>email and password</em></p>
<p>you can also use the same method above to create an authentication backend to authentify with a secure key or etc</p>
<p>e.g</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib.auth <span class="hljs-keyword">import</span> authenticate

<span class="hljs-comment"># authenticate username and password</span>
authenticate(request, username = username, password = password)
<span class="hljs-comment"># authenticate email and password</span>
authenticate(request, email = email, password = password)
</code></pre>
<p>let's create a superuser and test :</p>
<pre><code class="lang-batch">$ python3 manage.py shell
&gt;&gt;&gt; from django.contrib.auth import authenticate
&gt;&gt;&gt; authenticate(email = 'hamidipour97@gmail.com', password = 'my password')
&lt;User: amir-mohammad-HP&gt;
</code></pre>
<p>see the user model returned</p>
<h2 id="heading-build-authentication-form-use-with-django-loginview"><em>build Authentication Form</em> ( use with Django LoginView )</h2>
<p>unfortunately Django hadn't provide a built in email authentication system and so there is no default login by email but we can do the trick by our selves </p>
<p>we had just built out <a class="post-section-overview" href="#built_authentication">custom authentication backend</a>  and now we can use it to create our custom email authentication form to use with builtin <a target="_blank" href="https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.views.LoginView">django.contrib.auth.views.LoginView</a></p>
<p>let's create a file and name it <em>./authenticate/forms.py</em></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django <span class="hljs-keyword">import</span> forms
<span class="hljs-keyword">from</span> django.utils.translation <span class="hljs-keyword">import</span> gettext_lazy <span class="hljs-keyword">as</span> _
<span class="hljs-keyword">from</span> django.contrib.auth <span class="hljs-keyword">import</span> authenticate, get_user_model
<span class="hljs-keyword">from</span> django.utils.text <span class="hljs-keyword">import</span> capfirst
<span class="hljs-keyword">from</span> django.core.exceptions <span class="hljs-keyword">import</span> ValidationError

UserModel = get_user_model()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EmailAuthenticationForm</span>(<span class="hljs-params">forms.Form</span>):</span>
    <span class="hljs-string">"""
    Base class for authenticating users by email. Extend this to get a form that accepts
    email/password logins.
    """</span>

    email = forms.EmailField(
        label = _(<span class="hljs-string">"Email"</span>),
        widget = forms.EmailInput(),
    )
    password = forms.CharField(
        label=_(<span class="hljs-string">"Password"</span>),
        strip=<span class="hljs-literal">False</span>,
        widget=forms.PasswordInput(attrs={<span class="hljs-string">"autocomplete"</span>: <span class="hljs-string">"current-password"</span>}),
    )

    error_messages = {
        <span class="hljs-string">"invalid_login"</span>: _(
            <span class="hljs-string">"Please enter a correct %(email)s and password. Note that both "</span>
            <span class="hljs-string">"fields may be case-sensitive."</span>
        ),
        <span class="hljs-string">"inactive"</span>: _(<span class="hljs-string">"This account is inactive."</span>),
    }

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, request=None, *args, **kwargs</span>):</span>
        <span class="hljs-string">"""
        The 'request' parameter is set for custom auth use by subclasses.
        The form data comes in via the standard 'data' kwarg.
        """</span>
        self.request = request
        self.user_cache = <span class="hljs-literal">None</span>
        super().__init__(*args, **kwargs)

        <span class="hljs-comment"># Set the max length and label for the "emial" field.</span>
        self.email_field = UserModel._meta.get_field(UserModel.EMAIL_FIELD)
        email_max_length = self.email_field.max_length <span class="hljs-keyword">or</span> <span class="hljs-number">254</span>
        self.fields[<span class="hljs-string">"email"</span>].max_length = email_max_length
        self.fields[<span class="hljs-string">"email"</span>].widget.attrs[<span class="hljs-string">"maxlength"</span>] = email_max_length
        <span class="hljs-keyword">if</span> self.fields[<span class="hljs-string">"email"</span>].label <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            self.fields[<span class="hljs-string">"email"</span>].label = capfirst(self.email_field.verbose_name)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">clean</span>(<span class="hljs-params">self</span>):</span>
        email = self.cleaned_data.get(<span class="hljs-string">"email"</span>)
        password = self.cleaned_data.get(<span class="hljs-string">"password"</span>)

        <span class="hljs-keyword">if</span> email <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">and</span> password:
            self.user_cache = authenticate(
                self.request, email=email, password=password
            )
            <span class="hljs-keyword">if</span> self.user_cache <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
                <span class="hljs-keyword">raise</span> self.get_invalid_login_error()
            <span class="hljs-keyword">else</span>:
                self.confirm_login_allowed(self.user_cache)

        <span class="hljs-keyword">return</span> self.cleaned_data

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">confirm_login_allowed</span>(<span class="hljs-params">self, user</span>):</span>
        <span class="hljs-string">"""
        Controls whether the given User may log in. This is a policy setting,
        independent of end-user authentication. This default behavior is to
        allow login by active users, and reject login by inactive users.

        If the given user cannot log in, this method should raise a
        ``ValidationError``.

        If the given user may log in, this method should return None.
        """</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> user.is_active:
            <span class="hljs-keyword">raise</span> ValidationError(
                self.error_messages[<span class="hljs-string">"inactive"</span>],
                code=<span class="hljs-string">"inactive"</span>,
            )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.user_cache

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_invalid_login_error</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> ValidationError(
            self.error_messages[<span class="hljs-string">"invalid_login"</span>],
            code=<span class="hljs-string">"invalid_login"</span>,
            params={<span class="hljs-string">"email"</span>: self.email_field.verbose_name},
        )
</code></pre>
<p>this is just a copy of <a target="_blank" href="https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.forms.AuthenticationForm">django.contrib.auth.forms.AuthenticationForm</a> that overwritten for email</p>
<p>to use this form by django,need to include <a target="_blank" href="https://docs.djangoproject.com/en/4.1/topics/auth/default/#using-the-views">django.contrib.auth.views</a> in urlpatterns:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> django.contrib.auth.views <span class="hljs-keyword">import</span> LoginView
<span class="hljs-keyword">from</span> authenticate.forms <span class="hljs-keyword">import</span> EmailAuthenticationForm

urlpatterns = [
    path(<span class="hljs-string">"login/"</span>, 
        LoginView.as_view(authentication_form =  EmailAuthenticationForm), 
        name = <span class="hljs-string">'login'</span>
    ),
    ...,
]
</code></pre>
<p>NOTE : if you're not familiar with authentication views you can read <a target="_blank" href="https://github.com/amir-mohammad-HP/django4-SampleCode/tree/main/accounts%20URL%20customizing">URLs &amp; views authentication customizing</a></p>
]]></content:encoded></item></channel></rss>