Switch to toggle `LoginRequiredMixin` on/off

The LoginRequired mixin is an incredible feature which does a lot of heavy lifting and enables me to protect my website content from outsiders. The official Django docs explain how to require web visitors to login by adding it as part of a Class Based Views constructor through inheritance: Using the Django authentication system | Django documentation | Django.

However that doc is only really detailed enough to get it up and running. It’s simple and straight forward - - which is great. But what if I wanted to grant the Admin user in the Dashboard the option to turn a tickbox to turn the Required Login page on and off?

Is it possible to remove the protected gateway with the flick of a switch by the Admin?

If such a feature is not native to the official library of mixins, where might you people suggest if I were to implement this feature on my own? I’m just curious about the basic architecture of what that might look like.

Hey there!
I think that the first question you need to answer yourself is: why some route sometimes needs login protection, and sometimes not? This may lead you to think about the design decision of your app.

But answering your question, is possible to implement this behavior, but you’re going to need to do that yourself.
Creating the respective models, CBV, or decorators if using function based views.

Thanks for the reply. Although I am not quite sure I understand what you mean. What other potential design decisions are even possible? Where can I learn more about design approaches?

Would writing my own home-made middleware be necessary as well?

I mean, if you’re going to toogle the authentication, then you’re going to have a route that sometimes will be authenticated, and sometimes not. My question is: why would you want something like this? The answer of this question may help you see if you’re designing something wrong or not properly.

Just to be clear, you’re trying to remove the requirement of login, or remove the behavior of redirecting to the login page?

I am still misunderstanding.

In my usercase, there is a username/password protected ‘gateway’ page enforced by the LoginRequiredMixin. I was intending to grant the superuser the ability to remove the gateway with a switch (toggle check box on/off) in the Admin Dashboard which would instead enable Django serve a different unprotected landing page, further enabling public web visitors without usernames/passwords to travese downwards into the rest of the site and contents.

Ok i think that i get what you’re saying.
Doing this on the admin is totally possible, but you’re going to need to use a different mixin, one that “knows” when the login requirement is on/off.
And my point here is not whether you can or can’t do this, i’m trying to understand why you want to do this. Because, this is not a common thing, actually it’s really “wild” and prone to errors.
Imagine that you have some sensitive data on your site, and forgot to toogle the authentication back on?
Because normally when you make your views login protected is either:

  • You want to know which user is performing some action;
  • You want to protect some action to a specific type of user
  • You want to protect some data from being acessed publicly.

My homemade Django CMS blog web app in prod will house and contain some-what controversial philosophical perspectives and ideas. I have already posted the content and it’s protected with the Login Mixin. Since I am the superuser, only I have access. When I feel the Django website infrastructure is complete and as I continue to prepare the essay content, I intend to share my project (website and prod content) with a small select group of peers and colleagues. Maybe 6 people total.

I figure I can create a single user name and password combo for all 6 to share. So I don’t even need 6 different usernames and passwords. Other than my secured and locked down superuser credentials that I personally use, there will just be one username and pass for my 6 friends to share. The password won’t even adhere to the recommended built-in complexity of the LoginMixin. Instead I will just override the requirements by creating a user name and then with a password which is a 7 character long string with all lower case letters and no special characters and no numbers.

I am not trying to defend against a sophisticated adversary like Russian hackers or the NSA. The purpose of my gateway is merely to keep web crawlers and search engines out until my production content is ready to be revealed publicly.

Another way of looking at my needs for my CMS project is like that of a wedding photographer. When my brother got married a few years ago, the photographer took hundreds of photos. The photographer shared a link to her website which didn’t even require a username. It was just a 10 character long passphrase with weak entropy, but it is just enough to temporarily keep out Google from accessing the content.

Regardless of your intent, you really don’t want to do this.

If any one person has their computers compromised, then you’re in the position of changing that password for everyone wishing to access your blog. And, you won’t even know the source of whatever leak occurs.

Go ahead and create individual accounts for everyone.

But regardless of all this, you haven’t addressed the issue of why you think it’s necessary to create a mechanism to toggle the behavior of the LoginRequiredMixin. That mixin needs to be used on every page in the site - not just the landing page. Using it only on the landing page does not prevent access to any other page on the site.

So depending upon what your real underlying requirements are, you do have a couple choices.

You could:

  • Create your own subclass of the LoginRequiresMixin that checks the setting and alters its behavior accordingly.
  • Change your views to use the UserPassesTestMixin and make the status of that test depend upon your setting.

Side note: It’s far more common for a site to be attacked by a bot rather than it being directly targeted. If you have a small private site, you’ll find 90+% of your traffic consisting of blind attempts to run scripts and access pages containing known vulnerabilities on your host.

Hi KenWhitesell, thanks for your answer to my questions and for your detailed reply. I have a lot to say and more questions to ask.

Thank you for clarifying that LoginRequiredMixin is required in all CBV’s. All my CBV’s already include LoginRequiredMixin so every page on my site is safely protected.

For your first bullet point, when you say “create your own subclass” I am wondering: What level of proficiency in programming Python would be required to implement that? Could you share little bit more about where I could get started on that task with regards to the general architecture of changes or moficiations that might need to be made to achieve an Admin Dashboard switch to turn on/off the Login page?

For your second bullet point, thank you for sharing this reference to the official Python docs. The passage in the doc states:

class UserPassesTestMixin
When using class-based views, you can use the UserPassesTestMixin to do this.

You have to override the test_func() method of the class to provide the test that is performed. Furthermore, you can set any of the parameters of AccessMixin to customize the handling of unauthorized users:

from django.contrib.auth.mixins import UserPassesTestMixin

class MyView(UserPassesTestMixin, View):

    def test_func(self):
        return self.request.user.email.endswith('@example.com')

Based on the above quote and code sample, if I pass in the builtin UserPassesTestMixin and invoke the class method test_func, what should my endswith() paramater include? Does this passage suggest that when a web visitor enters a username with an email that ends with a certain domain name, then they can descend down into the rest of the website wihtout having to enter a password? Pardon my novice question but I find that this passage in the Django docs could provide a little more clarity. If I have misunderstood here, could someone kindly elaborate on how to leverage the UserPassTestMixin by invoking the test_func method to return a request to the user function for an email address ending in a specific domain?

I partially know what you mean. I have setup Google Analytics on a few websites the past and notice that it picks up traffic from all over the world. On these sites I have noindex,nofollow in my meta tags in the headers which is a superficial protection from bots. I get that. But what that means is the hits are not coming from Google and if I haven’t shared my website links with anyone, then all that traffic is coming frmo bots. All that HTML tag element does is keep my host and content out of Google search restults which is all I really needed for my other websites.

Those noindex,nofollow elements won’t protect against the 10-20 hits it receives on any given week from the kind of hostile bots you are referring to. But how ‘hostile’ are they really? Perhaps ‘attack’ might not be the right word. They aren’t actively trying to brute force my login page credentials. Instead the bots are merely ‘sniffing’ for vulnerabilities. In that case, I’m not too concerned because my content is uninteresting and insignificant to those threat actors even if they were to break into my Django login landing page.

I do my best to keep my Django stack fully updated with the latest versions. My dependabot plugin service for my source code repo on GitHub helps remind me when an Django update becomes available, keeping all potential exploits patched right away.

I realize that creating 1 user to be shared by 6 people sounds like a bad idea, generally speaking. But to elaborate on my use case, my intention is to demo my Django CMS website on my tablet at a dinner party. I will then provide the 6 people present with the same easy to remember and obvious username and password combination so they can try my website out themselves on their phones and then read some more of the content when they get home. Then at the 1 week mark, I will revoke the shared user access. So it’s all a temporary measure.

It’s also worth empahsizing this shared user will not have Django Admin access nor will they be able to write or post content. They will only have temporary viewing access rights.

Creating individual accounts for all 6 people with unique passwords, while much more secure and would align with best practices, it would also be very cumbersome and inconvenient. Is it really fair to expect me to create 6 usernames with a unique random password for each, then write them down on 6 different pieces of paper and then hand them out to the 6 individuals present?

Very little beyond what you already know. Keep in mind that every Model or CBV you create is a subclass of a parent class that you are modifying for your own purposes.

As the scope of what you’re looking to do for this is what I would refer to as "narrow and well-defined’, I’d probably take a direct approach.
General idea:

  • Create a model to contain the indicator of whether logins are required or not.
  • Create your admin page to allow for that indicator to be changed
  • Create the subclass for LoginRequiredMixin (Start by reviewing the source for it in django.contrib.auth.mixins - the mixin is only about 7 lines long.)
    • Change the dispatch method to have the check for is_authenticated to only be performed if your indicator defined above is set to do so
  • Alternative to the above - don’t subclass it, since this mixin only contains one function, and you’re replacing it. Just create a new class inheriting from AccessMixin with your new dispatch method.
  • Change all your views to use your new mixin

A change of terminology is necessary here.

This example isn’t “passing” UserPassesTestMixin here, it’s defining a new class, inheriting from both UserPassesTestMixin and view. It’s then overriding the test_func method within that class. And it’s not a classmethod - it’s not tagged as such and it’s accepting self as the first argument. (This, in general, is actually a pretty good example of what needs to be done for the LoginRequiredMixin above.)

As an example, don’t take what’s being done in the test_func literally. That function needs to return a True/False value, where True will allow the view to be executed.

Not at all.

First, this is performed on a per-view basis, exactly like the LoginRequiredMixin as described in my first response. So this would only apply to those views in which it’s being used.

Second, this specific implementation still requires that the user be logged in. They still needed to provide a valid username/password combination. That’s the only way that request.user is going to be populated with an object having an email address.

While I referenced just a specific section of that page in the docs, understanding how authentication works does require an understanding of that entire page with some other pages in the docs - along with a pretty good understanding of Python classes.

“Attack” is precisely the right word here. If any of those vulnerabilities are discovered and subsequently exploited, then they’re usually able to gain root access to that system.

Are they interested in your content? Probably not.

But what they are interested in is using your computer for their own purposes -

  • Crypto mining
  • Distribution of illegal materials
  • As another bot to be used for DDoS attacks against different hosts
  • As another bot to be used to continue to sniff for vulnerable targets
  • As a base for making attacks against specific targets

Depending upon how/where you host your server, this could end up

  • Costing you some significant amount of money (e.g. AWS),
    • Or legal fees if your host was discovered as part of an attack against a different host. You might be “not guilty”, but you’re still going to need to pay your lawyer.
  • Causing your host to be shut down due to violations of your Terms and Conditions,
  • Having your host blocked by any number of other sites

Most of these attacks are not against Django-related URLs, other than “/admin”. They’re typically against basic PHP-related urls or a small handful of specific frameworks such as Wordpress.
They’re also regularly probing for standard ports such as ssh, smtp (imap, pop3), and other services typically exposed to the world. (If you have ssh access to your host, are you checking those logs for attacks?)

I aggressively use fail2ban to block access to hosts attempting to access any of those known urls or ports - my ban list is sitting just less than 500 ip addresses. The point is, securing a host exposed to the internet requires a lot more than just protecting Django.

Yep - I see that. Seems reasonable for this specific situation.

But, to round this out -

Depending upon the context, I do it with groups of 15 - 20 on an occasional basis. (I used to do some teaching on a fairly regular basis. I’d have to create student accounts that would need to be good for a week.) If not hand-written, I’ll print the information either on the back of business cards or on an index card. Takes what - 5 minutes? Usernames can follow a simple pattern, and depending upon the expected life for these accounts, the password doesn’t need to be all that secure.

Hi @KenWhitesell,

Thanks for providing the general contours of how to write a LoginRequireMixin toggle feature for a web app. I am only able to understand your outline vaguely and loosely. I know you said that writing this feature is within reach of someone with a basic proficiency in Python, but I think I still have some ways to go. The intermediary step for me is to work on more exercism.org practice problems.

This is a long list of threat actors. I see what you mean. One basic line of defense against these types of attacks is to use an obfuscated admin/ route. Heroku has this really helpful feature which enables the admin/ address to be defined as a dynamic configuration variable inside the Heroku dashboard. What I have done is appended an ambiguous string of 12 random characters to the end of admin so the only way I can access it is via direct entry like with a bookmark. Since it’s a dynamic environmental variable, when I am testing locally I can still access the Django Admin Dashboard at: localhost:8000/admin/. Heroku is great.

You explained some further potential malicious activity carried out by hostile actors on the web:.

My web host is Heroku. There is shell access but it is not ssh. It’s a CLI tool Heroku develops in-house. I trust that on their side, Heroku as a power intermediary keeps their doors secure. The potential risk factor (and you’ll kill me when I tell you this but:) the local client-side package for this Heroku CLI tool that I have installed and am actively using in my development environment to interface with Heroku remotely is a few point releases out of date. I run Desktop Manjaro Linux locally and the package repository maintainer abandoned his responsibilities a few months ago and there hasn’t been anyone who stepped in to take his place. So while my client side shell access tool to interface with Heroku is out of date, I trust that Heroku on the server side keeps their shell access doors tightly secured with fail2ban-like precautions. It’s also worth mentioning that to get this client-server side shell access to connect, it requires 2FA with my mobile device which is an excellent feature that isn’t possible through ssh.

Oh, absolutely - Heroku does a fine job of managing fundamental security from the perspective of the host, and the changed admin URL does resolve 95%+ of Django-focused attacks.

Sounds to me like you’ve got that side of it well covered.