ALERT!

*please read the WHOLE article before copy and pasting everything!*

I figured it was time to come up with a custom authentication scheme for Cloud Endpoints, particularly, when building a RESTful API. Whether you’re developing for the web, or you’re developing for mobile – you’re going to need some way to authenticate requests from your clients. One method (and probably the most recommended), would be by using OAuth2. Take the headache out and just dump the stress on someone else’s shoulders. After all, Google or Facebook have some pretty broad shoulders. But what if you don’t want to do that route? What do you do? Well, this blog will answer that.

Custom Authentication

The first thing here to remember is that RESTful API’s are stateless. That means that all of the required information that the API needs to authenticate a user must be in every single request. We can’t store anything in the API instance – whatsoever.

Alright, now that we know what we can’t do, let’s talk about what we can do! Since we want to be able to authenticate across all types of devices, token authentication seems to be the best route. A token is just a long, random string passed between the server and the client so that the server knows at all times, who is making the request.

Overview

Let’s break this down into its components first:

  1. We need a two model classes. One to store the user (Users) and one to store the token (Tokens) itself.
  2. We need a way to authenticate a user via email and password first.
  3. After the user has been successfully authenticated, the server generates a token.
  4. Once the token has been generated, it gets added to the Tokens model, which has a KeyProperty back to the original authenticated user. It’s at this point that we set the lifespan of the token itself as well in that same model.
  5. Once this is all said and done, we pack the token up in some request (XML or JSON), and send it back to the authenticated user through HTTPS. This would either be a website or a mobile application.
  6. Once the user has received this token, it’s either stored as a cookie or within Android / iOS as some globally accessible variable.
  7. Now, we have a random token that has been safely generated from a securely authenticated user. We saved that token server side, and also transferred it to the client securely as well.
  8. Now all the client has to do is send that token through HTTPS and the server can use the Tokens model to determine who you are or what permissions you have!
  9. Done!

The Code

Assume that we received an email and a password from an HTTPS connection, we now have to: register a user, generate a token, log that user in, and send that token back to the user. Here we go!

Let’s create two database classes first. We’re going to do this using NDB.

The first one stores our user by email and also stores a password hash.

class Users(ndb.Model):
    '''
    Contains user information.

    '''

    email = ndb.StringProperty(required = True)
    password_hash = ndb.StringProperty(required = True)

The second one stores our token.

class Tokens(BaseModel):
    '''
    Contains tokens for authentication.
    '''

    token = ndb.StringProperty(required = True)
    user = ndb.KeyProperty(kind = 'Users', required = True)
    lifespan = ndb.DateTimeProperty()

Alright, it’s at this point that we need to register a user and generate a random token. Here’s how we do that:

Assuming this is our raw text:

    email = "email@example.com"

    password = "password123"

We’re going to use a SHA512 bit hash and a 256 bit salt to encrypt our password.

 

import random
import hashlib
from string import letters

def make_salt(length = 32):
    return ''.join(random.choice(letters) for x in xrange(length))

def make_pw_hash(email, pw, salt = None):
    if not salt:
        salt = make_salt()
    h = hashlib.sha512(email + pw + salt).hexdigest()
        return '%s,%s' % (salt, h)

Alright, now lets add the bulk of the code.

# NDB Model Libraries
from google.appengine.ext import ndb

# token libraries
import uuid
import hmac

try:
    from hashlib import sha1
except ImportError:
    import sha
    sha1 = sha.sha

class Users(BaseModel):

    '''
    Contains user information.
    '''

    email = ndb.StringProperty(required = True)
    password_hash = ndb.StringProperty(required = True)

    @classmethod
    def by_email(cls, email):
        user = cls.query(cls.email == email).get()
        return user

    @classmethod
    def by_token(cls, token):
        t = Tokens.query(Tokens.token == token).get()
        if t:
            u = Users
            return t.user.get()
        return False

    @classmethod
    def login(cls, email, password):
        # returns a user and a token value
        user = cls.by_email(email)
        if user and valid_pw(email, password, user.password_hash):
            t = Tokens()
            t.user = user.key
            t.token = t.generate_key()
            t.put()
            return [user, t.token]

    @classmethod
    def create_user(cls, email, password):
        u = cls.by_email(email)
        if not u:
            u = Users()
            u.email = email
            u.password_hash = make_pw_hash(email, password)
            u.put()
            t = Tokens()
            t.user = u.key
            t.token = t.generate_key()
            t.put()
            return [u, t.token]

class Tokens(ndb.Model):
    '''
    Contains tokens for authentication.
    '''

    token = ndb.StringProperty(required = True)
    user = ndb.KeyProperty(kind = 'Users', required = True)
    lifespan = ndb.DateTimeProperty()

    def generate_key(self):
        new_uuid = uuid.uuid4()
        # Hmac that beast.
        return hmac.new(str(new_uuid), digestmod=sha1).hexdigest()

Now I need to do some explaining here.

In the Users class, we added four @classmethod’s: by_email(), by_token(), login(), and create_user()

by_email() – gets a user by email

by_token() – gets a user by token

login() – logs in a user

create_user() – creates a user

And we added one instance method to the Tokens class: generate_key()

generate_key() – generates a random token

Alright! Now we’ve got everything we need. Remember, we have:

    email = "email@example.com"

    password = "password123"

How to Register a User

  1. We call user_token = Users.create(email, password)
  2. user_token would be an array containing [user_object, token] (the token is a string)
  3. Now we can just take that token and send it back to the client.

How to Login a User

  1. We call user_token = Users.login(email, password)
  2. user_token would be an array containing [user_object, token] (the token is a string)
  3. Likewise, we can now take that token and send it to the client.

Authenticating Requests

So now, the client has a copy of the token and the server has a copy of the token (which is linked to the user object within the Users model). To authenticate requests, we just pass that token string to:

by_token(token_string)

This returns a user object to our endpoint and we are good to go!

That’s it! I really hope this helps one or two people out there at least! If you would like me to go more in depth or to clarify some portion or just chat in general – drop me a comment below. I’m always happy to talk code.

CHEERS!

 

Advertisements