Proposal: Manually login without authenticate

228 views
Skip to first unread message

Paulo Gabriel Poiati

unread,
May 22, 2015, 11:36:21 AM5/22/15
to Django Developers
Hello guys,

I want to discuss the current login workflow in django. As it is we need to call `authenticate` before `login` because we need to set the authentication backend in the user model. We can use login without authenticate if we set the backend attr manually, but this need some implementation knowledge of how authentication backends works.

PROPOSAL

django.contrib.auth.login
Only set the backend in the http session if the user has the attr.

request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(user, 'backend'):
    request.session[BACKEND_SESSION_KEY] = user.backend

django.contrib.auth.get_user
If the backend is not set iterate over all the backends in `settings.AUTHENTICATION_BACKENDS` and return the first found.

I can think in two drawbacks of this implementation but as far as my knowledge goes neither of them is a big deal.

- Performance, in the worst case we are trying all the backends this can take some time depending on the backend provider.

- Two backends can share the same user identifier and thus we will be using the first declared in the settings.

What do you guys think?

Thanks,
Paulo Poiati.





Tim Graham

unread,
May 22, 2015, 11:44:47 AM5/22/15
to django-d...@googlegroups.com
Could you elaborate on what use case you are trying to solve?

Paulo Gabriel Poiati

unread,
May 22, 2015, 11:53:17 AM5/22/15
to django-d...@googlegroups.com
Of course Tim,

One instance is if you need to login the user after registration:

class RegistrationView(CreateView):
    ...
    def form_valid(self, form):
        user = form.save()
        login(self.request, user)
        # redirect

I don't have the user password because it's an one way hash and thus I can't call `authenticate`. The only solution I can think is hacking the auth system (setting the backend manually in the user model).

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/fbbd5579-a5e9-4370-8943-75204f334016%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Paulo Gabriel Poiati

unread,
May 22, 2015, 11:58:06 AM5/22/15
to django-d...@googlegroups.com
Maybe this is not the perfect example because we have the plain password in the form. Another one is if a backoffice user can log as any user of the system.

James Brewer

unread,
May 22, 2015, 12:21:15 PM5/22/15
to django-d...@googlegroups.com
Hey Paulo,

As you mentioned, the raw password should still be present in the form, along with the username. You can use these as parameters for `authenticate()`.

More to your original point, I would be interested in knowing why `authenticate()` and `login()` are separate methods. There is no information on this in the docs (that I could find). Is there a use case where you want to authenticate a user without logging them in?

Collin Anderson

unread,
May 22, 2015, 12:24:59 PM5/22/15
to django-d...@googlegroups.com
One advantage of authenticate() is that it isn't coupled to a request and is roughly stateless. Maybe an authenticate_and_login() would be nice. :)

Paulo Gabriel Poiati

unread,
May 22, 2015, 12:30:54 PM5/22/15
to django-d...@googlegroups.com
Good point James,

It make sense if you could login the user without the credentials (proposal).

Marc Tamlyn

unread,
May 22, 2015, 12:38:55 PM5/22/15
to django-d...@googlegroups.com
Here is a related ticket for doing this in tests, where frankly I don't care about authentication. https://code.djangoproject.com/ticket/20916

Carl Meyer

unread,
May 22, 2015, 1:14:47 PM5/22/15
to django-d...@googlegroups.com
On 05/22/2015 09:44 AM, Tim Graham wrote:
> Could you elaborate on what use case you are trying to solve?
>
> On Friday, May 22, 2015 at 11:36:21 AM UTC-4, poiati wrote:
>
> Hello guys,
>
> I want to discuss the current login workflow in django. As it is we
> need to call `authenticate` before `login` because we need to set
> the authentication backend in the user model. We can use login
> without authenticate if we set the backend attr manually, but this
> need some implementation knowledge of how authentication backends works.

I've run into this more than once in an OAuth or SSO situation, where
I've got confirmation from the identity provider of the user's identity
and I just want to log them in, no further authentication necessary.
Usually I just manually annotate the user with a `backend` attribute and
then call `login`, but I agree with Paulo that login's dependence on
that annotation is a bad API.

I don't prefer Paulo's proposal of "just try all backends", though; I
think it's best if any user persisted in the session is always persisted
along with explicit knowledge of which auth backend should be used to
fetch that user in future requests. Failing to do that could potentially
result in security problems, I think, depending on details of the
configured auth backends.

What I would suggest instead is to add an optional `backend` argument to
`login`, defaulting to `None`. If provided, that argument would always
be used, and the `backend` annotation on the user would not be required
or even checked.

At some point, I could see possibly deprecating the `user.backend`
annotation system entirely, in favor of having `authenticate` return a
(user, backend) tuple, and passing the backend explicitly through the
form back to the login view and into the `login` function. I think that
would be better, but it would also be much more invasive and require
deprecation paths on several APIs, so may not be worth it.

In any case, adding the optional `backend` argument to `login` is a
small step in that direction that doesn't introduce any
backwards-incompatibility and makes the "manually log a user in" use
case much more straightforward.

Carl

signature.asc

Paulo Gabriel Poiati

unread,
May 22, 2015, 3:49:47 PM5/22/15
to django-d...@googlegroups.com
I understand your points Carl, but I'm more inclined to think about the average django developer and the new comers. Most people don't know or don't want to know what an Authentication Backend is, they just want to write their app. That is why I don't like exposing this kind of detail in the client code. I mean, we can add an optional param to login (backend) for people like you and me but IMO it shouldn't be mandatory and we should fallback to the backend iteration if it isn't specified.



--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.

Carl Meyer

unread,
May 22, 2015, 4:07:35 PM5/22/15
to django-d...@googlegroups.com
Hi Paulo,

On 05/22/2015 01:49 PM, Paulo Gabriel Poiati wrote:
> I understand your points Carl, but I'm more inclined to think about the
> average django developer and the new comers. Most people don't know or
> don't want to know what an Authentication Backend is, they just want to
> write their app. That is why I don't like exposing this kind of detail
> in the client code. I mean, we can add an optional param to
> login (backend) for people like you and me but IMO it shouldn't
> be mandatory and we should fallback to the backend iteration if it isn't
> specified.

Logging a user in without authenticating them first is an advanced
technique with serious security implications. If someone "doesn't know
or doesn't want to know what an Authentication Backend is," then they
have no business doing this, and we should absolutely make it very hard
for them to do it without learning about authentication backends first.

Which backend is persisted with the user in the session is not a minor
implementation detail. It's critical information, because it determines
how and from where that user will be loaded on future requests, which
could have wide-ranging effects on their permissions and authorization.

I agree with you that the current system of annotating the user object
with an attribute that usually isn't present is bad API, and that it
would be good to have a supported, documented API for this use case. But
I strongly disagree with you that the backend is an implementation
detail which should be hidden in this API. The backend should be more
visible in this API than it is now, not less visible.

Carl

signature.asc

Ryan Hiebert

unread,
May 22, 2015, 4:15:01 PM5/22/15
to django-d...@googlegroups.com
I agree completely.

The couple times I've found myself muddling through creating an auth backend, I've had a hard time figuring out what needs to go in the login view, and what should be in the authentication backend. I think that making it clearer what the distinction is between login() and authenticate() is would help that immensely, and making it clear that the backend need not be set by authenticate would also make it clearer.

Ryan

Paulo Gabriel Poiati

unread,
May 22, 2015, 4:42:42 PM5/22/15
to django-d...@googlegroups.com
Tks Carls,

I completely agree with the API design, it can be improved. What I'm really trying here is to keep things simple.

I don't see that importance in keeping the authentication backend information in the http session (maybe I'm missing something), we can always use some kind of audit to handle this kind of information.

Let's think straight, we don't need to tell the Auth Backend if settings.AUTHENTICATION_BACKENDS has only one element.

So, I have a new proposal:

If the application has only one backend we always infer it in the login function. If it isn't, the client needs to provide one explicitly.

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.

Carl Meyer

unread,
May 22, 2015, 4:53:06 PM5/22/15
to django-d...@googlegroups.com
Hi Paulo,

On 05/22/2015 02:42 PM, Paulo Gabriel Poiati wrote:
> I completely agree with the API design, it can be improved. What I'm
> really trying here is to keep things simple.
>
> I don't see that importance in keeping the authentication backend
> information in the http session (maybe I'm missing something),

It is possible to have multiple authentication backends with overlapping
user IDs; some backends may not return User objects that exist in
Django's User table. A user (in the broader authentication backend
sense) is uniquely defined only by both the authentication backend and
the ID; using only the ID could result in the wrong user being loaded
from the wrong backend on a subsequent request.

> we can
> always use some kind of audit to handle this kind of information.

I'm not sure what this means.

> Let's think straight, we don't need to tell the Auth Backend if
> settings.AUTHENTICATION_BACKENDS has only one element.
>
> So, I have a new proposal:
>
> If the application has only one backend we always infer it in the login
> function. If it isn't, the client needs to provide one explicitly.

I don't have a problem with defaulting to the only backend, if there is
only one.

Carl

signature.asc

Unai Zalakain

unread,
May 22, 2015, 4:58:35 PM5/22/15
to django-d...@googlegroups.com
Hi Paulo!

>If the application has only one backend we always infer it in the login
>function. If it isn't, the client needs to provide one explicitly.

Why not pass the single auth backend into the login function? It makes
the design and the documentation much simpler.

--
unai
signature.asc

Tai Lee

unread,
May 23, 2015, 8:27:01 AM5/23/15
to django-d...@googlegroups.com, un...@gisa-elkartea.org
I agree, why not always pass the single auth backend into the login function?

If the backend is inferred by a single value in the settings and not stored alongside the user ID, what would happen to existing users who are already logged in when a second backend is added to the settings and deployed? Django would no longer know which backend to use to fetch the authenticated user?

Cheers.
Tai.

Steven Berry

unread,
May 23, 2015, 12:58:45 PM5/23/15
to django-d...@googlegroups.com
Hey all,

A note on Carl's first suggestion. I currently override the authenticate function with my own that does something similar. Instead of passing in a single backend I pass in an iterable (usually a set for O(1) checks) that optionally overrides settings.AUTHENTICATION_BACKENDS.

I haven't run into any issues with this, and the default method has always been a pain point in my applications.

Regards,
Steven

Marc Tamlyn

unread,
May 23, 2015, 1:38:21 PM5/23/15
to django-d...@googlegroups.com
If the backend is inferred by a single value in the settings and not stored alongside the user ID, what would happen to existing users who are already logged in when a second backend is added to the settings and deployed? Django would no longer know which backend to use to fetch the authenticated user?

I believe the intention would be to store the single backend against the user ID on login, so this would be safe for sessions loaded after a second backend is added. Obviously code using this convention would need to be updated when more than one backend is used.

I think this would be a good plan, I reckon that most sites use only one auth backend anyway.

 

Cheers.
Tai.


On Saturday, May 23, 2015 at 6:58:35 AM UTC+10, Unai Zalakain wrote:
Hi Paulo!

>If the application has only one backend we always infer it in the login
>function. If it isn't, the client needs to provide one explicitly.

Why not pass the single auth backend into the login function? It makes
the design and the documentation much simpler.

--
unai

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.

Carl Meyer

unread,
May 23, 2015, 2:38:35 PM5/23/15
to django-d...@googlegroups.com
On 05/23/2015 11:37 AM, Marc Tamlyn wrote:
> If the backend is inferred by a single value in the settings and not
> stored alongside the user ID, what would happen to existing users
> who are already logged in when a second backend is added to the
> settings and deployed? Django would no longer know which backend to
> use to fetch the authenticated user?
>
>
> I believe the intention would be to store the single backend against the
> user ID on login, so this would be safe for sessions loaded after a
> second backend is added. Obviously code using this convention would need
> to be updated when more than one backend is used.
>
> I think this would be a good plan, I reckon that most sites use only one
> auth backend anyway.

Right. I think we always need to store the backend in the session; I'm
OK with the backend to store being chosen automatically for sites with
only a single backend configured (at the moment when the user is logged in).

So the proposed logic for the login() function would look something like:

1) Use the value of the `backend` argument, if provided.
2) Use the value of the `user.backend` annotation, if present.
3) Use the only configured backend, if there is only one.
4) Raise ValueError("You have multiple authentication backends
configured; you must provide the `backend` argument to `login`.")

(I don't think the error would even need to mention the annotation
option; if you're using authenticate+login you'd never hit the error
anyway, and if you're not then the new `backend` arg is the preferred
API we should advertise.)

Carl

signature.asc

Unai Zalakain

unread,
May 24, 2015, 4:13:46 AM5/24/15
to django-d...@googlegroups.com
>3) Use the only configured backend, if there is only one.
>4) Raise ValueError("You have multiple authentication backends
>configured; you must provide the `backend` argument to `login`.")

What about defaulting the backend argument to Django's default auth
backend? This way most of the sites will not need to worry about it
while the API is maintained simple and without "magic".

Those sites that use some other auth backend would need to pass it
explicitly into the login function. If they don't do that (or, for that
matter, if you pass an auth backend which is not present in your
settings) they would get an error.


--
unai
signature.asc

Paulo Gabriel Poiati

unread,
May 25, 2015, 10:22:11 AM5/25/15
to django-d...@googlegroups.com
This looks perfect to me Carl. I will create a ticket in the tacker and work on it.

Tks all you guys for the feedback and suggestions.
 
--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers.

Carl Meyer

unread,
May 25, 2015, 11:33:30 AM5/25/15
to django-d...@googlegroups.com
We can't default the `backend` argument to anything but a sentinel
value, because if it's supplied, it should take priority, but if it's
not supplied we need to maintain backwards compatibility with existing
behavior, where the `user.backend` annotation is used.

Carl

signature.asc

Unai Zalakain

unread,
May 25, 2015, 12:49:22 PM5/25/15
to django-d...@googlegroups.com
>We can't default the `backend` argument to anything but a sentinel
>value, because if it's supplied, it should take priority, but if it's
>not supplied we need to maintain backwards compatibility with existing
>behavior, where the `user.backend` annotation is used.

So, to sum app, the behaviour would be:

- If the backend argument is not the sentinel, use that backend.
- Else, if `user.backend` is present, use that backend.
- Else, use Django's default auth backend.

I think it's somewhat important to be explicit about the backend we want
to use if that backend is not Django's default one. By defaulting to the
backend defined in settings if there is only one present we are
introducing a bit more of magic.


--
unai
signature.asc

Carl Meyer

unread,
May 25, 2015, 1:17:27 PM5/25/15
to django-d...@googlegroups.com
Hi Unai,

On 05/25/2015 10:52 AM, Unai Zalakain wrote:
>> We can't default the `backend` argument to anything but a sentinel
>> value, because if it's supplied, it should take priority, but if it's
>> not supplied we need to maintain backwards compatibility with existing
>> behavior, where the `user.backend` annotation is used.
>
> So, to sum app, the behaviour would be:
>
> - If the backend argument is not the sentinel, use that backend.
> - Else, if `user.backend` is present, use that backend.
> - Else, use Django's default auth backend.

...unless Django's default auth backend is not in
AUTHENTICATION_BACKENDS, in which case raise an error?

> I think it's somewhat important to be explicit about the backend we want
> to use if that backend is not Django's default one. By defaulting to the
> backend defined in settings if there is only one present we are
> introducing a bit more of magic.

I don't agree that the default auth backend deserves special treatment.

If a project is using an AUTHENTICATION_BACKENDS with only the default
backend in it, and then at some point they need to make some small tweak
(case-insensitive lookups, perhaps), so they subclass it and now have an
AUTHENTICATION_BACKENDS setting that still has only one backend,
basically the same as before but now a subclass of it, I don't think
this should mean that now they suddenly have to explicitly specify it in
a call to `login()` whereas before they didn't. I think special handling
for the default backend is _more_ magical and unexpected than special
handling of length-one AUTHENTICATION_BACKENDS.

I'm open to the argument that we should always require explicit
selection of backend (no default in case of only one backend defined).
This trades some convenience in the common case for more explicit
behavior. I have no strong feeling either way there. But I'm opposed to
special treatment for the default backend.

Carl

signature.asc

Unai Zalakain

unread,
May 25, 2015, 2:01:30 PM5/25/15
to django-d...@googlegroups.com
>If a project is using an AUTHENTICATION_BACKENDS with only the default
>backend in it, and then at some point they need to make some small tweak
>(case-insensitive lookups, perhaps), so they subclass it and now have an
>AUTHENTICATION_BACKENDS setting that still has only one backend,
>basically the same as before but now a subclass of it, I don't think
>this should mean that now they suddenly have to explicitly specify it in
>a call to `login()` whereas before they didn't. I think special handling
>for the default backend is _more_ magical and unexpected than special
>handling of length-one AUTHENTICATION_BACKENDS.
>I'm open to the argument that we should always require explicit
>selection of backend (no default in case of only one backend defined).
>This trades some convenience in the common case for more explicit
>behavior. I have no strong feeling either way there. But I'm opposed to
>special treatment for the default backend.

My reason to propose such a solution was because I think that the
backend should be *always* specified. As a tradeoff for the case where
you "don't mess with auth backends", I thought that, if unespecified,
Django's default one could be used. But you are right when you say that
it can be even more magical than handling the case where the auth
backends setting has length one.

But, if possible, I'm in favour of following a deprecation path and
requiring the backend argument to be mandatory.



--
unai
signature.asc
Reply all
Reply to author
Forward
0 new messages