I'm using an existing database for my latest Django project, so unless I change my Models or the Django auth code, it's going to be rather difficult to merge the two.
Rather than messing with the existing auth backend, I'm planning on just writing my own authentication app.
Anyway, all of my previous authentication apps have been written in PHP, where basically i just throw everything in session variables and verify them on every page... Here's what I'm a bit confused about. It appears that when a user is authenticated/logged in, the entire user is added to a session, but I can't figure out where or how that is occurring.
In the default Django login function, it's assigning user to request.user ... is this being saved as a session variable somehow or is it just passed into the next view? If it is just being passed to the next view, how are future requests authenticated without requiring further login requests?
The default Django auth login is below..
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request.
"""
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.id:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.id
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
I also tried to follow the user_logged_in.send(), which is in django.dispatch.dispatcher.send but I'm not entirely sure what that's supposed to do either.
def send(self, sender, **named):
"""
Send signal from sender to all connected receivers.
If any receiver raises an error, the error propagates back through send,
terminating the dispatch loop, so it is quite possible to not have all
receivers called if a raises an error.
Arguments:
sender
The sender of the signal Either a specific object or None.
named
Named arguments which will be passed to receivers.
Returns a list of tuple pairs [(receiver, response), ... ].
"""
responses = []
if not self.receivers:
return responses
for receiver in self._live_receivers(_make_id(sender)):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
return responses
Basically what I'm looking for is for someone to explain an efficient way to save user session data in Python that does not depend on the Django framework. A little run-through of the Django authentication would be nice as well.
HTTP is stateless; regardless of the server used, the framework or language, there is no intrinsic way for an HTTP client to say "this request is part of that session". That's part of the design of HTTP.
So sessions are always a feature of the web application; either supported by the a web app framework or implemented in the app itself. The most usual way for a stateful session to be created from the stateless protocol is with cookies; Clients will store cookies at the request of a server and return those same cookies to that server in future requests.
Session data can be serialized and stored in the cookie itself, but that's both insecure (secret information could be forged or eavesdropped), and inefficient (takes bandwidth even though the individual bytes are of no use to the client), and so the preferred solution is to use an opaque (and even better, single use) session key is stored in a cookie, and the web application will store session data out of band; either in memory, in the filesystem, or a database backend, or some other option.
django takes care of most of this transparently in "middleware", modules that modify incoming requests and outgoing responses. The auth middleware will read a cookie and check if that represents a logged in user, and if so, add a user object to the request; it also attaches cookies to responses when a user gets logged in. the session middlware works in a similar fashion, checking for a cookie, and reading the session data from wherever it was stored between requests, and also grabbing session data from responses and storing them, along with setting a cookie to associate the client's session with the session data it just stored.
Since both of these features are useful, independent of each other (I tend to avoid sessions, but usually use some kind of authentication), they do not depend on each other. Session-like authentication is implemented in a similar manner as sessions, but authenticated users are not stored in "The Session", nor are sessions attached to "The authenticated User".
You might not think so, but django's authentication system is designed to be extended; if you already have a database of valid users you'd like to authenticate against, it's very simple to add a new auth backend that dovetails neatly into the standard django auth application (which means you can also use other applications that depend on it in turn).
Related
I am running a django app with a REST API and I am protecting my API endpoint with a custom permission. It looks like this:
class MyPermission(permissions.BasePermission):
def has_permission(self, request, view):
host = request.META.get('HTTP_HOST', None)
return host == "myhost.de"
)
The idea is that my API is only accessible via "myhost.de".
Right now I am testing this with pytest. I can set my headers with:
#pytest.fixture()
def request_unauth(self, client):
result = client.get(
"myurl",
headers={'HTTP_HOST', 'myhost.de'},
content_type="application/json",
)
return result
def test_host(request_unauth):
assert request_unauth.status_code == 200
Since I can easily fake my headers I assume that this might also be easily done with other tools? How can MyPermission be evaluated from a security perspective?
Thanks so much for any help and hints. Very much appreciated.
Checking the Host header like that does not make sense, and will not protect against 3rd party clients as you described in comments. An attacker can create an arbitrary client and send request to your API, and that request can (and will) include the correct Host header as any other legitimate request.
Also based on your comments, you want to authenticate the client application, which is not technically possible, as it has been discussed many times. With some work (the amount of which you can influence somewhat) anybody can create a different client to your API, and there is no secure way you could prevent that, because anything you include in your client will be known to users (attackers), and will allow them to copy it. You can and probably should authenticate your users though, check access patterns, implement rate limiting, revoke user access in case of suspicious activity and so on - but this is all based on user authentication.
You can also prevent access from a client running in a standard browser on different domain, by sending the correct CORS headers (or not sending CORS headers at all) in your API.
I'm using Flask builtin session mecanism.
Here is my understanding of session mecanism (with flask) :
all session data are stored in a signed cookie (with app.secret_key)
when a session data is modified, the cookie is changed
session's data are protected against write client side (due to signature) but not against read
Imagine the following scenario :
In my session I put a variable try_number=3
Each time the user make an action, a decrease this number
If this number is equal to 0, action is forbidden
The user connect to the application for the first time, the application send a Set-Cookie: sesssion=Flask.sign("try_number=3"), let's call this cookie COOKIE_A.
The user perform his first action, he send COOKIE_A, the application reply with Set-Cookie: sesssion=Flask.sign("try_number=2"), let's call this cookie COOKIE_B.
Now, if the user perform another action, but doesn't use COOKIE_B but COOKIE_A again (using curl for exemple), the cookie is still signed, and will be handled by the server, with try_number=3.
Therefore, only using the COOKIE_A for all operation, he will be able to "spoof" session mecanism, and make unlimited action with the same session.
Is there any builtin mecanism to prevent this ?
(I'm not talking about snippet for using sqlite / redis, but builtin solution)
This is not a failure of the security of Flask's cookies, it's a failure of your counter design. There is no built in protection against replay attacks.
You can shorten the expiration time of the session cookie. This doesn't really solve the problem, it just makes the window smaller. It also makes the session inconvenient for regular use, which would annoy your normal users.
Ultimately, you'll have to store some information on the server and check against it. You could send a nonce with every request and keep a store of which ones have been sent back, ignoring ones that have been seen before. You could also just store all session information (except some identifying key) on the server side, so it can't be re-sent.
While looking for a good and efficient way to have a session in my app, I found GAE Boilerplate and GAE Sessions.
GAEB is amazing but very vast for my needs: I don't need Federate Login nor a default User structure, but I like the design structure and the way they solved some issues (routes, forms,...).
GAES is quite simple but powerfull to treat sessions. The most I like is the way it stores everything in a cookie, in this case, it stores the full user entity in a cookie so in the next page calls, no other datastore hits are done: the user data is always read from the cookie (this need, obviusly to update the data if the user updates something, which is not usual).
In the other hand, GAEB stores only the user ID and then retrieves, on each page call, the username and the user email. This is part of the code for the BaseHandler it uses (GAEB uses NDB model):
#webapp2.cached_property
def username(self):
if self.user:
try:
user_info = models.User.get_by_id(long(self.user_id))
if not user_info.activated:
self.auth.unset_session()
self.redirect_to('home')
else:
return str(user_info.username)
except AttributeError, e:
# avoid AttributeError when the session was delete from the server
logging.error(e)
self.auth.unset_session()
self.redirect_to('home')
return None
Same for email, and in the render_template function it does this:
def render_template(self, filename, **kwargs):
.... some code.....
# set or overwrite special vars for jinja templates
kwargs.update({
'app_name': self.app.config.get('app_name'),
'user_id': self.user_id,
'username': self.username,
'email': self.email,
... more vars ...
})
kwargs.update(self.auth_config)
It seems that reads 2 times (one for username and one for email) from the datastore, because this funcs makes models.User.get_by_**field**(long(self.user_id))
The only thing I don't know exactly what means is the #webapp2.cached_property, that maybe means that all this datastore reads are done from a cache and really don't hit the datastore.
Can someone tell me what is the better solution to save hits to the database? It seems that it is better to have all the user data in a cookie (obviously, secured) and don't hit the datastore on every page call, but maybe I'm mistaken (I'm relatively noob with GAE) and all this reads to datastore are cached and, then, for free.
Saving session data in the cookie is highly discouraged:
It has to be transfered with each request (slow on mobile connections)
The HTTP Header size you can transfer to the GAE is limited (64Kb if i remember correctly) - thats the upper bound of data you could store
Even if you encrypt and sign your session, you would still be vulnerable to reply attacks (you cannot perform a logout safely)
I don't know the implementations you mentioned, but we have an session implementation in our CMS, see https://bitbucket.org/viur/server/src/b2e9e3dca3adabee97e1761d630600a387a02c44/session.py?at=master .
The general idea is to generate a random string (used as a session identifier).
On session load a datastore "get by key" is performed (which is cached, so if that object is still in memcache, it wont hit the datastore at all).
And saving is only performed if the data stored inside the session changed or the session has not been
updated for the last 5 minutes.
Then you can copy the values of your user object into the session and wont have an additional datastore request.
I've looked around for this but can't seem to find a canonical answer. I'm trying to follow best practices. Maybe I'm thinking of it the wrong way.
I'm thinking of my API users as two different types: developers and end users, with separate django models in separate applications.
Developers will build clients for the API, and have access to certain resources of the API without the need of users login in. To limit their access, I would require them to register and in exchange give them an API key. We would also dogfood this to say, build a site frontend using Angular and iOS app.
Once those developers build their API clients, users of my site, which have already created a user account, will use the API clients created by developers. In the request from those clients I would expect a developer name, api_key as well as username/password (digest, if its our own trusted client and oauth token for thid party developers). This will require to check 1) developers are allowed to use the API by checking their APIKey, and 2) authenticate the end user. Is this possible in tastypie?
Am I going about this the wrong way? How would I do the double authentication?
We run a production site with this exact scheme. Of course you'll have to do your own tunning. But the general idea is good. You could have some OAuth inplace too, but we've found that it's not worth it. OAuth is too complicated for most cases.
I'll explain roughly what we do.
This is the App/developers part:
We identify "apps" (iOS, Android, BB, the site). Each app has an ApiClient instance model. The ApiClient has three attrs: name, public key, and private key.
We exchange the public and private keys through a safe channel with the ApiClient owner (the app).
The app must send every request indicating the public key and a signature generated with the private key (using hmac).
Everytime we get a request, we get the public key from the request, look it up in the DB, see what App it belongs too (the name) and check the signature. If everything is ok, the request is fulfilled.
For the user authentication part:
To authenticate a user we use other model ApiKey (provided by tastypie). Each user has an ApiKey. That model stores a unique (we could say random) string. When the user gets to the app he/she logs in into your API. The app should issue a request similar to this one:
POST /api/v1/login/
{
'username': 'XXX',
'password': 'XXX'
}
(please note that it always need to pass the previous public/private key auth)
If the user provided the right credentials we return an ApiKey unique key.
Every following request made by the app in behave of that user must include the key. That's the way you identify which user is trying to do each action.
An example of this last part:
User Jon logs in in the iOS app. (using regular username and password)
The app sends the request:
POST /api/v1/login/
{
'username': 'jon',
'password': 'snow'
}
We have a login API method. We check if the user exists and if the pass is ok. Suppose it's ok.
We sent the ApiKey info:
200 OK
{
'username': 'jon',
'key': '$123$'
}
The app has authenticated the user. It needs to use those credentials.
The user tries to do something in your app. Suppose he tries to get the datetime from your app. The app will issue this request:
GET /api/v1/date/
Authorization: ApiKey jon:$123$
That's it. It's not super safe. The ApiKeys are not invalidated. But that's because we create our own internal Apps. It's worth to note that we borrow some stuff from Tastypie from this. Check this out: http://django-tastypie.readthedocs.org/en/latest/authentication.html#apikeyauthentication
This is tangental, but you may want to check out drf-keypair-permissions.
It's a Django module that uses asymmetric public/private keypairs to sign and verify the HTTP request using pre-shared keys. It can pair each public key with a user so the authorization doubles as a login, and can be extended to manage API throttling.
It supports a few algorithms, including RSA-SHA and elliptic curve, and keys can be managed in the admin area.
It uses the IETC Cavage-12 draft standard for processing the Authorization signature
I'm building an application in which the client pings the server every now and then (let's not get into why). When the server handles these requests, it checks whether the client is logged in or not using request.user.is_authenticated()
It looks something like this:
def handle_ping_request(request):
if request.user.is_authenticated():
# Do something...
else:
# Do Something else...
I've noticed that sometimes the server receives a log-in request immediately followed by a ping request (from the same user). The client is then successfully logged-in, the response returns with a new session ID (of the logged in user) and (I guess that) the old session-ID (of the anonymous user) is removed. When the ping request is processed, its request contains the old session-ID. Thus the ping request returns with a third session ID and on the next request the client makes, the client is no longer logged in.
My log-in code looks something like:
if not request.user.is_authenticated():
user = auth.authenticate(...credentials...)
if user and user.is_active:
auth.login(request, user)
Do you have any suggestions on how to avoid this problem? Preferably without involving the client.
Thanks.
It's probably too messy to handle this on the server because you'll have to create some kind of a semaphore system that will also try to guess if any ping is currently from a client that is also being authenticated. My suggestion would be simply to change the client code not to ping while it's waiting for a response to its login request.
You could create an alternative to the standard contrib.auth.login method that keeps the same session id, rather than generating a new one, either by using a custom authentication backend that doesn't generate a new key, or by creating a custom session backend that overrides the cycle_key() method in contrib.sessions.base to reuse the same key.
BUT: think about what you might be opening yourself up to by reusing the same session key - depending on where this system is in use, you'd be making yourself more open to session hijacking (ie: there's only one session id to sniff), as well as potentially issues where caches may return the unauth page content instead of the auth page content because the sessionid is technically the same and the cache can't tell the difference between the two situations, etc, etc, etc.
In short: there's a reason it's built to work, by default, the way it does.