1

I'm trying to authenticate via Github on my Django site. This is what I came up with:

import string
import random
import urllib

from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.crypto import constant_time_compare
from django.contrib.auth import login

from rauth import OAuth2Service
from mongoengine.django.auth import User

from utils import make_absolute


SESSION_KEY = '_oauth_access_token'
SESSION_STATE = '_oauth_state'

github = OAuth2Service(
    client_id=settings.GITHUB_APP_ID,
    client_secret=settings.GITHUB_API_SECRET,
    name='github',
    authorize_url='https://github.com/login/oauth/authorize',
    access_token_url='https://github.com/login/oauth/access_token',
    base_url='http://github.com/')


def random_string():
    return ''.join(random.choice(string.ascii_letters + string.digits)
                   for _ in xrange(random.randint(27, 49)))


def flush_and_set(request, key, value):
    if key in request.session:
        if request.session[key] != value:
            request.session.flush()
    else:
        request.session.cycle_key()
    request.session[key] = value


def start_pipeline(request):
    state = random_string()
    flush_and_set(request, SESSION_STATE, state)
    return redirect(github.get_authorize_url(
                        redirect_uri=make_absolute(reverse('auth-pipeline-end')),
                        state=state))


def end_pipeline(request):
    if not constant_time_compare(request.session[SESSION_STATE],
                                 request.GET['state']):
        return redirect('home')
    session = github.get_auth_session(data={'code': request.GET['code'],
                                            'redirect_uri':
                                            make_absolute(reverse('home'))})
    flush_and_set(request, SESSION_KEY, session.access_token)
    user_data = session.get('https://api.github.com/user?' +
                            urllib.urlencode({'access_token':
                                              session.access_token})).json()
    username = user_data['login']
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        user = User(username=username)
    for field in ('email',):
        d = user_data[field]
        if d:
            setattr(user, field, d)
    user.backend = 'mongoengine.django.auth.MongoEngineBackend'
    user.save()
    login(request, user)
    return redirect('home')

I have two questions:

  1. Is it secure and safe done this way?
  2. Should I set an expiry on the session? Because as it is now it seems to me that it will never expire.

Note: I'm also using rauth: https://rauth.readthedocs.org/en/latest/

rubik
  • 8,814
  • 9
  • 58
  • 88

1 Answers1

1

Not as secure as it could be and some details are lacking.

    • For a much more secure state value use SystemRandom.
    • Are your sessions are cookie based? They won't be by default and they should not be.
    • Is the redirection back protected by HTTPS?
    • Are all your requests to GitHub over HTTPS? After token is obtained too?
  1. Default expiry is 2 weeks but OAuth 2 access tokens normally expire much quicker. However I believe GitHub tokens never expire so an improvement would be to expire the session on close and delete the token when no longer needed.

That said, I would strongly encourage you to use python-social-auth instead of rolling your own GitHub login as that project have many more eyes on it and have had for a while.

ib.lundgren
  • 1,524
  • 14
  • 15
  • Thank you for your answer! So no, the sessions are not cookie based (I get they should be?). My site is not on HTTPS, however all requests towards Github are on HTTPS. I was using django-social-auth before, but I encountered problems and I chose to roll my own solution (I only need Github login). I'll try again maybe. – rubik Sep 15 '13 at 09:16
  • They should not be cookie based, will edit answer. Also if your site is not on https the auth code could be sniffed and used to get an access token if they manage to steal/guess your client secret. – ib.lundgren Sep 15 '13 at 11:25
  • There are of course many other attacks that can be made if you don't use HTTPS so if you are going to use OAuth 2, use HTTPS. – ib.lundgren Sep 15 '13 at 13:03
  • Thanks for the suggestions. I'll look into HTTPS for my domain. – rubik Sep 15 '13 at 16:36