Python (Tornado)


Table of Contents:

Prerequisites
Tornado SDK Installation
Run the Tornado Demo App
Create your own App


Prerequisites


Creating an app involves two basic steps:

  1. Register/create an app in the portal and obtain credentials (this is detailed in the 'Register / Create a New App' menu section)
  2. Configure your app using an SDK, making use of these credentials

It is recommended that you first create a demo/test app with a redirect uri of http://127.0.0.1:5000/login in the portal, to enable you to run the SDK demo app and test the SDK functionality.

In the portal, when you create a new app, you will be issued with the client ID and client secret credentials that you need to specify when building your app with the SDK. These are found in the app settings screen:

app settings page

Note that you can control the Login Methods available (QR Code requires customer usage of the mobile app. While Browser Login enables logging in within the desktop browser, without the mobile app)

When creating your app in the portal, you also must specify a redirect_uri endpoint which comes from the url where you are hosting your app:

add app

The SDK will automatically create the redirect uri by appending '/login' to your url. You, of course, are in charge of what the base url of your app is. So it could be http://testapp.com/login, http://12.34.567.89:8080/login or http://127.0.0.1:5000/login as above.


Tornado SDK Installation


  1. The SDK has certain system package dependencies which need to be installed. For Ubuntu 14.04:

    sudo apt-get install build-essential (for compiling code in dependencies)
    sudo apt-get install python-dev or sudo apt-get install python3-dev (depending on python version used)
    sudo apt-get install libssl-dev
    sudo apt-get install libffi-dev
    sudo apt-get install python-setuptools or sudo apt-get install python3-setuptools (depending on python version used)

  2. You can now download or clone the SDK from:

    https://github.com/miracl/maas-sdk-tornado

  3. Install the SDK.

    Go to the root directory:
    cd maas-sdk-tornado

    Then run:
    sudo python setup.py install

    Note that this is a python 3 SDK. If using python 2.7 it may be necessary to comment out the from __future__ import unicode_literals line at the top of setup.py.


Run the Tornado demo app


Running the pre-configured demo app will help you make sure that you have all your settings correct and so will be able to proceed with confidence toward developing your own application.

Note that the demo app is configured to run on 127.0.0.1:5000. This is controlled by the line app.run(port=5000) in server.py. This means that the demo app will, by default, work with an app that has been set up in the portal with a redirect uri of http://127.0.0.1:5000/login. If you wish to change this, just edit the app.run(port=5000) line.

  1. From the root directory, you now need to go to the directory containing the demo app:

    cd samples

    Here you will find three files:

    1. index.html
    2. miracl.json
    3. server.py
  2. Open the miracl.json file and configure with your client id, client secret and redirect_uri values, as set up in the Create a New App menu section.

    For security, the client ID and secret for a genuine app should not be stored in clear text in a config file. This has only been done here for simple demo purposes. For a production scenario, the client ID and secret should be programmatically accessed via an encrypted API

    {
    "client_id": "CLIENT_ID",
    "secret": "CLIENT_SECRET",
    "redirect_uri": "http://127.0.0.1:5000/login"
    }

    Substitute CLIENT_ID, CLIENT_SECRET with your correct values and enter the redirect_uri as above.

  3. Open the index.html file and, towards the end of the file, make sure the mpad script has the correct url in order to communicate with the authentication server (note that it begins with 'mcl.cdn.mpin.io'):

    <script src="https://mcl.cdn.mpin.io/mpad/mpad.js" data-authurl="{{ auth_url }}" data-element="btmpin"></script>
  4. Now run the server script:

    python server.py

    Your app should now be running on http://127.0.0.1:5000/ (or whichever port you have set!)

    image1

    From here you will be prompted to register, create an identity and log in to the app.

    Once logged in you will be greeted by the refresh/logout page:

    image2


Creating your own app


What follows is a detailed breakdown of the structure and code used in creating the sample app, which will give you a broad understanding of how to begin creating your own app. Note that the snippets of code which follow can be put to use, and you can also find the full files (complete with commented code) in the samples folder.

To start developing your own app, create a project folder within the maas-sdk-tornado root folder. The basic steps involved, as per the sample app, are:

  • Create a main web page with the login button
  • Create a credentials file
  • Create a main server script to contain the code for dealing with the Miracl Client object and communicating with the platform

1. Create the main web page and the login button

Note that the use of col-md divs here assumes use of the bootstrap framework, as does the use of the {% %} tags to receive flash messages in the standard bootstrap categories of 'success', 'info' and 'danger'.

In your index.html file, first listen for messages coming from your main script:

  
  <div class="row">
      <div class="col-md-12">
          {% if messages %}
              {% for message in messages %}
                  <div class="alert alert-{{ message["category"] }}">
                      {{ message["message"] }}
                  </div>
             {% end %}
          {% end %}
      </div>
  </div>
    

Then an if retry check is made to listen for the retry message coming from your main script. The retry message means that an attempted login has failed (as will be seen later) and your script will have provided an authorization url. In this case a btmpin div is created to contain the login button (note that the wording for this is automatically populated by mpad.js hence there is no text entered in the btmpin div):

  
  {% if retry %}
      <div class="col-md-12">
          <div id="btmpin"></div>
      </div>
  {% end %}
  

The next if is_authorized check is to listen for the is_authorized message, in which case display the user email and id information and the refresh and logout buttons which initiate a redirect to the "/refresh" and "/logout" endpoints:

  
  {% if not retry %}
      <div class="row">
          {% if is_authorized %}
              <div class="col-md-4">
                  <b>E-mail:</b> {{ email }}<br/>
                  <b>User ID:</b> {{ user_id }}<br/>
              </div>
              <div class="col-md-4"></div>
              <div class="col-md-4">
                  <a href="/refresh" class="btn btn-primary action">Refresh user data</a>
                  <a href="/logout" class="btn btn-primary action">Log out</a>
              </div>
          {% else %}
              <div class="col-md-12">
                  <div id="btmpin"></div>
              </div>
          {% end %}
      </div>
  {% end %}
  

The last else section of the above snippet then creates a btmpin div to contain the login button (note that the wording for this is automatically populated by mpad.js hence there is no text entered in the btmpin div).

Finally, just before the closing </body> tag, create the button itself:

  <script src="https://mcl.cdn.mpin.io/mpad/mpad.js" data-authurl="{{ authurl }}" data-element="btmpin"></script>  

This makes use of the mpad.js library to send the authentication information to the server:

The parameters passed in this script are:

  • data-element: the login button ID (corresponds with <div id="btmpin">)
  • data-authurl: the authorization URL (this passes the client_id and redirect_uri to the authentication server). Each SDK has a 'Get Authorization Request URL' method for obtaining this.

2. Create a Credentials file

For security, the client ID and secret for a genuine app should not be stored in clear text in a config file. This has only been done here for simple demo purposes. For a production scenario, the client ID and secret should be programmatically accessed via an encrypted API

In your project folder, you can create a .json file named to store the credentials for your app. For the sample this is called miracl.json:

  {
  "client_id": "CLIENT_ID",
  "secret": "CLIENT_SECRET",
  "redirect_uri": "REDIRECT_URI"
  }

Substitute the correct values for your app, as discussed in 'Prerequisites' above.

3. Create a main server script

Now create a main .py server script (), with the following main elements:

  1. Libraries to import:
    import miracl_api_tornado as auth
    from tornado import web, ioloop, gen
    import json
  2. Read Credentials

    The following def reads the credentials from the miracl.json file:

    def read_configuration():
    config_file = open("miracl.json")
    config = json.load(config_file)
    config_file.close()
    return config

    The read_configuration method is now available for creating the Miracl Client object, as seen below.

  3. Setup message handling and template rendering for communication with index.html

    Create the message handler to save messages in a secure cookie (note that categories are according to the default Bootstrap categories of success, info, warning, danger):

    def flash_message(handler, category, message):
      # Get messages string from cookie
      messages_json = handler.get_secure_cookie("messages")
      if not messages_json:
          # If messages was not set, create empty list
          messages = []
      else:
          # If messages was set, decode messages
          messages = json.loads(messages_json)
      # Add new message to list
      messages.append({"category": category, "message": message})
      # Serialize and put modified list into cookie
      handler.set_secure_cookie("messages", json.dumps(messages))      

    Render the template to start output to the client:

    def render_template(handler, **kwargs):
        # Read messages from cookie
        messages_json = handler.get_secure_cookie("messages")
        if not messages_json:
            messages = []
        else:
            messages = json.loads(messages_json)
    
        # Set messages cookie to empty list
        handler.set_secure_cookie("messages", "[]")
    
        # Set potentially missing flags for template model (defaulting to False)
        if "retry" not in kwargs:
            kwargs["retry"] = False
        if "is_authorized" not in kwargs:
            kwargs["is_authorized"] = False
    
        # Render template
        handler.render("index.html", messages=messages, **kwargs)
  4. Working with the Handlers

    The Tornado MiraclMixin uses RequestHandler functionality to handle requests and preserve state between calls.

    At the end of the sample server script, you will see the Miracl Client object and Request Handler routes are set up to enable management of the Authorization Flow:

    if __name__ == "__main__":
        settings = {
            'cookie_secret': 'secret',
            'xsrf_cookies': True,
            'debug': True,
            'miracl': read_configuration()
        }
        app = web.Application([
            (r"/", MainHandler),
            (r"/login", AuthHandler),
            (r"/logout", LogoutHandler),
            (r"/refresh", RefreshHandler)
        ],
            **settings
        )

    First of all, initiate a Callback route handler which operates at the "/login" endpoint. After a user has made a login attempt, this will check if their login attempt has been successful and send either a success message if successful, or a danger message and a retry=True if login has failed:

    class AuthHandler(auth.MiraclAuthRequestHandler):
      def on_auth_success(self, user_data):
          # Notify user about success
          flash_message(self, "success", "Successfully logged in!")
          # Redirect to default route
          self.redirect("/")
    
      def on_auth_failed(self):
          # Notify user about failure
          flash_message(self, "danger", "Login failed!")
          # Render retry template
          render_template(self, retry=True, auth_url=auth.get_login_url(self))

    Now create the main handler which uses the .is_authenticated method to check if the user is authenticated, in which case an access token is returned, and the get_email and .get_user_id methods can be used to retrieve user information. Otherwise, use the get_login_url method to send the authorization url to the client and enable creation of the login button:

    class MainHandler(web.RequestHandler):
        def get(self):
            if auth.is_authenticated(self):
                # If authenticated, render template with user data
                email = auth.get_email(self)
                user_id = auth.get_user_id(self)
                render_template(self, is_authorized=True,
                                email=email,
                                user_id=user_id)
            else:
                # If not authenticated, render template with login button
                render_template(self, auth_url=auth.get_login_url(self))
    

    Important Note on User Management

    In terms of managing your users, it is important to note that, in the process of providing a secure login solution, the service has also registered your users with a confirmed user email and user ID. Once a user has been authenticated it is possible to make use of the above get_email and .get_user_id methods to return string values.

    This removes a considerable amount of pain from the process of managing users and databases!

    For example, if you have a SQL user database and you want to make a check to see if a user is present, or needs added as a new user, then it is possible to make use of the above methods.

    Another example would be if you want to present a web form to capture more information that is needed to provide a user with access to your product features. Here you can use the above methods to prepopulate the ID and email address field, and you do not need to initiate a 'verify by email' process, as this has already been done by the service.

    A LogoutHandler can now be created:

    class LogoutHandler(web.RequestHandler):
        def get(self):
            auth.logout(self)
            flash_message(self, "info", "User logged out!")
            self.redirect("/")  

    And a RefreshHandler can be used to clear authentication if a token has expired:

    class RefreshHandler(web.RequestHandler):
        @gen.coroutine
        def get(self):
            # User data executes http request - we need to yield
            yield auth.refresh_user_data(self)
            self.redirect("/")

    Note that refresh clears userInfo from the session, but leaves the access token unchanged. This means that user info can be requested without performing a fresh authorization.

    Finally, set the app to listen on, e.g. port 5000:

    # Set listening port
    app.listen(5000)
    # Start IOLoop
    ioloop.IOLoop.current().start()      

    Note that app.listen(5000) means that the app will be listening on http://127.0.0.1:5000 and means that a redirect_uri value of http://127.0.0.1:5000/login will function correctly. Obviously, you should change this to match the base url of your app and it should match what you have entered as the redirect URI for your app in the portal.

Top