wiki:UserAuthentication

User Authentication

Logged in user query

Thiblo uses Django  authentication framework. Request handlers that need to check user identity do this by accessing user attribute of  request object, which is passed as the first parameter to every request handler. It contains an instance of  User model, which is used to represent every user in the system. Calling is_anonymous() method should return False for logged-in users, but this is used rarely in code (see below). This object can be compared directly with User objects retrieved from the database.

Note that this functionality is only available in these  middlewares that are lower than AuthenticationMiddleware in settings.MIDDLEWARE_CLASES list.

If handler functionality should not be accessible to anonymous users, it is guarded by decorator. Django's  login_required redirects anonymous user to login page, and takes back to the requested URL after successful authentication. There is also our own lib.shortcuts.login_required which simply returns HTTP Forbidden — it is used to limit access to the views that are only retrieved via XHR, either by Webmore or account page.

Recognizing authenticated user

Authentication framework uses  sessions to store authentication status, and sessions are stored in browser cookies. Session cookie (its name determined by SESSION_COOKIE_NAME setting, sessionid by default) contains only cryptographically crafted identifier of actual data stored in the database on server. Data is a serialized ( pickled) dictionary-like object. Different authentication back-ends use different key names to store authentication data (see below).

Authentication

Thiblo supports two different methods of authentication: default username/password scheme and  OpenID. In the code each method is supported by an appropriate  authentication backend. All backends used by Thiblo are specified in the settings.py file:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'apps.account.backends.OpenIDBackend',
)

ModelBackend provides the basic authentication scheme that checks the Django users database. OpenIDBackend is a backed written by us, it provides OpenID authentication scheme to Thiblo.

Authentication with username and password

In order to start the authentication process we need to send a POST request with arguments username=...&password=... to the URI /auth/signin/.

To authenticate a given username and password we use django.contrib.auth.authenticate() function. It takes two keyword arguments, username and password and returns a  User object if authentication was successful (i. e. both username and password were correct). In any other way, it returns None.

Next, we should check if user account is active by checking is_active property.

After successful authentication, we should log user in. In order to do this, we use django.contrib.auth.login() function that takes two arguments: an  HttpRequest object and a User object. It saves the user's ID in the session, using  Django's session framework (see below for more information about how and where Django stores its session information).

User is now logged in and every part of code can their identity by accessing user attribute of the request object, as mentionted in section 1, Logged in user query.

In case of a repeated login attempt of the same user with correct credentials, only the expiration date is modified within the same session. No new session is created. If a different user logs in with correct credentials, it will also only modify the corresponsing session data, without creating a new session.

The code for this authentication scheme is located in the apps.account.auth.signin method.

Authentication with OpenID

In order to start OpenID authentication process we need to send a POST request with argument url=... to the URI /auth/openid/discover/.

For OpenID backend we use  Python OpenID Library. Each OpenID authentication request consists of three parts:

  • discovery, where we check if user provided an URL to the real and valid OpenID server and get a redirect URL. (apps.account.auth.discover)
  • redirect, where we redirect the user to the authentication page on their OpenID server with some data from us. (static/wcb/wcb.js:277)
  • complete, where we receive a response and check its result. (apps.account.auth.complete)

For discovery part we use openid.consumer.consumer.Consumer object. Its constructor requires two arguments: dictionary-like session object and any object that implements OpenID Store interface (we use openid.store.sqlStore.MySQLStore).

Discovery begins after calling the Consumer.begin() method that takes OpenID URL as its only argument. It returns a result object on success and throws an openid.consumer.discover.DiscoveryFailure exception on failure.

After a successful result we need to generate an URL where we redirect our user. We also need to include information about our system and return path there. This URL is generated by the redirectURL() method of the result object. It takes two parameters: current host base URL and a return path.

Next, we should redirect the user and wait for the response. Response is a simple redirect back with the status encoded in the GET data.

After response is received we create a Consumer object with just the same parameters as in the discovery part. Next we call Consumer.complete() object to complete authentication and get a result. It takes two arguments: a dictionary-like object with GET parameters received and a return path we provided in the discovery part.

Next steps are similar to the basic authentication scheme: we use django.contrib.auth.authenticate, then check is the user is active and then call django.contrib.auth.login to log user in and store information in the session. The only difference is that here we use different arguments for authenticate function: instead of username and password, we use openid_answer keyword argument. The value of the openid_answer argument is a result of Consumer.complete() method mentioned above. User is now logged in.

Automatic registration

With OpenID scheme we automatically register new users. So, if authenticate function returned None but OpenID's result.status property is equal to success we should create a new user, then authenticate and log in him again (without discovery and redirection parts). It basically means that we call authenticate() and login() methods again and with same arguments.

Where Django stores its session data

The Django sessions framework is entirely, and solely, cookie-based. It does not fall back to putting session IDs in URLs as a last resort. However, the only cookie that the session framework uses is a single session ID; all the session data is stored in the database, in a table called django_session which has three straightforward fields: session_key, session_data and expire_date.

In code, session is represented by a dictionary-like object request.session.