Custom login - UniMOOC/AAClassroom GitHub Wiki

New student registration

One of the things we wanted to change from the 2014 version of the Activate platform was the way the students authenticate on the platform. Specifically we wanted to scrap the default App Engine Google login and use the Google+ Sign-in system.

This feature has both a frontend and a backend component.

Frontend

We have to include the G+ Sign in button somewhere. Once the user is logged in we will set a header with the id_token of the user with every request that we want to authenticate.

config.headers['Id-Token'] = user.idToken;

(This example by itself won't do much. It's a part of the AngularJS app)

Once every requests has it's Id-Token header we are ready to authenticate them in the server.

Backend

To make a URL endpoint authenticated we can use the decorator gpluslogin located in modules.um_students.utils.py like so:

class ProgressAPIHandler(UMBaseHandler):
    """ Handlers for /api/progress"""

    @gpluslogin
    def get(self):
        """ Retrieves the progress of a student

            Returns the progress of the authenticated student
        """
        progress = self.student.get_course_progress()
        self.write_json(progress)

Every method decorated with gpluslogin can access the authenticated user with self.student.

The decorator does the following:

def gpluslogin(fn):
    """ Decorator to inject a student by their Google id token
    """
    def inner_fn(handler, *args, **kwargs):
        try:
            # 1
            id_token = handler.request.headers.get('Id-Token')
            if not id_token:
                return handler.return_with_error(
                    401, 'Id-Token header is required')
            # 2
            jwt = AuthenticationService.um_verify_id_token(id_token)

            if not jwt:
                return handler.return_with_error(401, 'Id-Token not valid')

            # 3
            email = jwt['email']
            handler.student = Students.get_by_email(email)

            if not handler.student:
                return throw_some_error()
        
        except AppIdentityError as ex:  # 4
            import logging
            logging.error(str(ex))
            return handler.return_with_error(401, 'API Identity error')

        return fn(handler, *args, **kwargs)
    return inner_fn
  1. Gets the token from the headers

  2. Verifies that it's a valid token (more on this later) and if it's not we return with an error

  3. Gets the email from the unencrypted token

  4. If the unencryption throws an AppIdentityError (which means that the token is not for our application) we throw an error.

The AuthenticationService gets the Google certs from memcache and tries to unencrypt the token. If for some reason it fails it updates the certs.