How do I make signup page available only for logged in staff users in Django allauth?

I’m very new to Django.

I used allauth to make email verification and user management system simple.

I want a system where only admins (staff users) can signup users.

But as it is now signup page is only available for not logged in users.

How do I make signup page available only for logged in staff users in Django allauth?

What I tried:

I tried to add custom view, url and template to customize access to signup page and form.

I added this on urls.py:

   path('accounts/signup/',views.user_register_view, name='signup'),

And this on views.py:

@login_required
def user_register_view(request):
    if request.user.is_staff:
        return render(request, "account/signup.html")
    else:
        reverse_lazy('users:dashboard')

And added a template file in template/account/signup.html, just copied the text from here:

And I added a custom text <br>THIS IS A CUSTOM TEMPLATE</br> just to see if my custom template is viewed.

What happened is just that when I sign in as admin, it instantly redirects to the signup page despite I set LOGIN_REDIRECT_URL = 'users:dashboard' in settings.py

And while only admins can access the accounts/signup page, alle the fields disappeared. This is how it looks like when you view the source:

<br>THIS IS A CUSTOM TEMPLATE</br>
<h1>Sign Up</h1>

<p>Already have an account? Then please <a href="">sign in</a>.</p>

<form class="signup" id="signup_form" method="post" action="/accounts/signup/">
  <input type="hidden" name="csrfmiddlewaretoken" value="muuodB6QqTD1BBxfIj7VW16qvjx1S7OUwoUf0xBNy6WuaLSE03228uMRxjJ2COjJ">
  
  
  <button type="submit">Sign Up &raquo;</button>
</form>

How do I make signup page available only for logged in staff users in Django allauth?

What url, form, template, and view are you using to handle the “sign in” process when you sign in as admin?

I use the same allauth default templates, urls and views sign in as admin or as any user. It’s not Django-admin interface.

This is how the urls.py looks like:

urlpatterns = [

    path('', views.home_page_view, name='home'),

    path('dashboard/',views.dashboard_view, name='dashboard'),

    path('accounts/signup/',views.user_register_view, name='signup'),
    ]

And this is how views.py looks like:


# users/views.py

from django.contrib.auth import login

from django.http import HttpResponseRedirect

from django.shortcuts import redirect, render

from django.urls import reverse, reverse_lazy

#from users.forms import CustomUserRegisterForm

from .forms import CustomSignupForm

from django.contrib.auth.decorators import login_required, user_passes_test

from users.models import User

from django.contrib.messages.views import SuccessMessageMixin

from django.views.generic.edit import CreateView

def user_is_admin(user):

    if user.is_authenticated and user.is_staff:

        return True

    else:

        return False

def home_page_view(request):

    return render(request, 'users/home.html')

@login_required

def user_register_view(request):

    if request.user.is_staff:

        return render(request, "account/signup.html")

    else:

        reverse_lazy('users:dashboard')

@login_required

def dashboard_view(request):

    return render(request, "users/dashboard.html")

While admins can register users on Django-admin interface, there are some problems here:
You can’t permit admin to add user but prevent him change the user details.
You can’t implement “email verification” when adding user on Django-admin.
If a staff has “permission” to do something on users, like delete users, then they are permitted to delete all users even if it’s another staff or superuser.

Actually, you can’t prevent a superuser from doing anything when using the default permissions system. See django.contrib.auth | Django documentation | Django and Using the Django authentication system | Django documentation | Django

Otherwise, for all the functionality you wish to implement, see Admin actions | Django documentation | Django along with all the ModelAdmin methods and the Admin site methods available for you to override.

As another note, your user_is_admin function is meaningless. If a person is not authenticated, their session is assigned to the AnonymousUser, which by definition is not staff.

Edit: Additionally, your signup.html template is expecting to render a form. You are not creating a form in your view, nor are you passing the instance of the form to the template through the context. You may want to review Working with forms | Django documentation | Django

When I prevent a staff to delete (or add or change) another user, like not give him the permission to delete another user, he can’t delete anyone in admin interface.
When I give a staff permission to delete, he can delete anyone, even other staff users and superusers.

How can I give a staff member permission to delete other users, but not staff users and superusers? I can’t find a method to do this.

See the set of “has_” methods, starting at The Django admin site | Django documentation | Django. You can implement those methods in any ModelAdmin object to grant or prevent access to any specific instance of the Model.

1 Like

Could you show me an example?

Is that how is it supposed to work:

def has_change_permission(self, request, obj=None):
        if obj:
            if isinstance(obj, User):
                if obj.is_staff:
                    return False
        return True

What about letting him add, but not change, user?

And when adding user, could we make the password system generated (also random generated password) ?

For these calls, obj is the specific instance of the object that is attempting to be retrieved or edited. The person making the request is available from the user attribute of the request object.

So, for has_change_permission... you might have something like this:

def has_change_permission(self, request, obj=None):
    if obj:
         # Some logic goes here to determine if `request.user` 
         # should be allowed to make a change to `obj`

Let’s look at a trivial case - you want to limit it so that a person can only edit their own User object. So in this case, obj is going to be the instance of the User table that the person selected to edit:

def has_change_permission(self, request, obj=None):
    if obj:
        if obj.pk == request.user.pk:
            # The user can edit themself
            return True
        # The user can't edit anyone else
        return False
    # We need the general test to return true,
    # because every user can change at least one object
    return True

The key is that you can make these conditionals as intricate as you want.

You can. This is fundamentally a bad idea, but Django’s not going to prevent you from doing it.

1 Like

I did that later, rendered the CustomSignUp form, but then the form doesn’t work. I can make the form accept POST data and register user etc. but it won’t be amazing like allauth’s system. To make it work like allauth’s system I have to write all the back-end code. And what’s the purpose of using allauth if I’m going to write everything myself anyway?

I didn’t find a solution to the original problem, but I found a simple workaround:
In settings.py:
ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS = False
So that both logged-in and non-logged-in users can view the original form.

Then I put simple logic in the template file so only staff users can view the register form. Others can see just a sorry message. You can see the code below for demonstration.

Since CSRF_token is used to register new user, I assume it prevents other users can’t just write the form on the frond end and register whoever they want even if they are not logged in. Else it would be very easy to hack. But I’m not sure about that.

Do you think it’s a good idea, or that’s too vulnerable?


{% extends "base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Signup" %}{% endblock %}
{% block content %}
<br>THIS IS A CUSTOM TEMPLATE</br>
{% if user.is_authenticated and user.is_staff %}
<h1>{% trans "Sign Up" %}</h1>
<p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>
<form class="signup" id="signup_form" method="post" action="{% url 'account_signup' %}">
  {% csrf_token %}
  {{ form.as_p }}
  {% if redirect_field_value %}
  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
  {% endif %}
  <button type="submit">{% trans "Sign Up" %} &raquo;</button>
</form>
{% else %}
Only admins are allowed to register new users
{% endif %}
{% endblock %}

Because you’re not really “using” allauth - at least not the way it’s designed or intended to be used. It’s not surprising that you’re having these kinds of problems with it.

The CSRF token has nothing to do with authentication. It does not prevent anything.

1 Like

The CSRF token has nothing to do with authentication. It does not prevent anything.

How? In all templates of allauth they use CSRF token in the forms, CSRF tokens used in auth forms too. I thought it was processed on back-end to make sure the CSRF token is equal to CSRF provided on back-end using some user data (session data or anything like that) before processing the form submit.

Wouldn’t that prevent any user to just write a form in HTML on the front end and submit the data?

Yes, but it’s not related to authentication. It has no relationship to the user who currently is (or may not even be) logged on. Even anonymous users get a CSRF token for submitting forms. The CSRF token is intended as a defense against a cross-site request forgery (hence CSRF) only. See Cross Site Request Forgery protection | Django documentation | Django

1 Like

Yes, I understand. But as I remember it’s used to prevent forms made by other people (hackers) in other sites to submit data into your site for your site.

If it prevents anonymous or other users (other than users specified in the logic, also staff users) to just build a simple HTML form and register user, then I think it’s made the purpose. I don’t have to build a very secure system like a bank account, but it shouldn’t be so laughably insecure.

Correct. It prevents me from setting up a page that you are viewing that would submit a form to site “X”. I can’t launch an attack on site “X” from your browser. (Or, usually more accurately, I can’t attack you based on your information available on site “X”.)
However, it does not prevent me from setting up a page that I am viewing from submitting a form to site “X”. Nothing prevents me from launching an attack on site “X” from my browser.

And relying upon the CSRF_Token alone is laughably insecure.

1 Like

So better to let staff use admin interface to register new users?

But I just want to know this before leaving this thread:
This workaround prevents any non-staff user (logged in or not) to just write a form in HTML on the front end and register new user?

That’s a UI/UX/Business Rule issue. There’s no technical reason to prefer one option over another, assuming the options are all properly secured.

What workaround are you referring to here?

What workaround are you referring to here?

The workaround I described in this post :How do I make signup page available only for logged in staff users in Django allauth? - #9 by STProgrammer

Your link is pointing to a message showing a template and not a view. The template provides no security at all - it only defines what is rendered on the page when it’s sent to the browser. It has no effect on what’s posted.

Most definitely, it does NOT:

1 Like

Yes. I just edited the template. I just use the default view of allauth. I don’t know how their view works.

Then no, you haven’t prevented anything.

1 Like