Since the sprawl of mobile apps and web services began, the need to create new usernames and passwords for each app or service started to become annoying and as it proved out decreases the overall security. Hence we decided to bet our authentication on the popular social media platforms (Facebook, Twitter, and Google) but wanted to make sure that we protect the authentication tokens on our side. Maybe in a later post I will go into more details what the pros and cons of this approach are but for now, I would like to concentrate on the technical side.

Here are the constraints or internal requirements we had to work with:

  • We need to support multiple social media platforms for authentication (Facebook, Twitter, and Google at minimum)
  • We need to support the web as well as mobile clients
  • We need to pass authentication information to our APIs but we also need to follow the REST guidelines for not maintaining state on the server side.
  • We need to make sure that we validate the social media auth token when it first reaches our APIs
  • We need to invalidate our own token after some time
The flow of events is shown on the following picture and the step-by-step explanations are below.

Authenticate with the Social Media site

The first step (step 1.) in the flow is to authenticate with the Social Media Site. They all use OAuth, however, each implementation varies and the information you receive back differs quite a lot. For details how to implement the OAuth authentication with each one of the platforms, take a look at the platform documentation. Here links to some:

Note that those describe the authentication with their APIs but in general the process is the same with clients. The ultimate goal here is to retrieve an authentication token that can be used to verify the user is who she or he claims to be.

We use Tornado Web server that has built-in authentication handler for the above services as well as generic OAuth handler that can be used for implementing authentication with other services supporting OAuth.

Once the user authenticates with the service the client receives information about the user as well as an access token (step 2. in the diagram) that can be used to validate the identity of the user. As mentioned above, each social media platform returns different information in the form of JSON object. Here are anonymized examples for the three services:

It is worth mentioning some differences related to the expiration times. Depending on how you do the authentication you may receive short-lived or long-lived tokens, and you should pay attention to the expiration times. For example, Twitter may respond with an access token that never expires ("x_auth_expires":"0"), while long-lived tokens for Facebook expire in ~60 days. The expiration time is given in seconds and it is approximate, which means it may not be exactly 60 mins or 60 days but a bit less.

Authenticate with the API

Now, that the user has authenticated with the Social Media site we need to make sure that she also exists in our user database before we issue a standardized token that we can handle in our APIs.

We created login APIs for each one of the Social Media platforms like follows

GET https://api.ourproducturl.com/v1.0/users/facebook/{facebook_user_id}
GET https://api.ourproducturl.com/v1.0/users/google/{google_user_id}
GET https://api.ourproducturl.com/v1.0/users/twitter/{twitter_user_id}

Based on what Social Media service was used to authenticate the user, the client submits a GET request to one of those APIs by including the authorization response from step 2 as part of the Authorization header for the request (step 3 in the diagram). It is important that the communication for this request is encrypted (ie. use HTTPS) because the access token should not be revealed to the public.

On the server side, few things happen. After extracting the Authorization header from the request we validate the token with the Social Media service (step 4).

Here are the URLs that you can use to validate the tokens:

  • Facebook (as well as documentation)
    https://graph.facebook.com/debug_token?input_token={token-to-inspect}&access_token={app-token-or-admin-token}
  • Google (as well as documentation)
    https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={token-to-inspect}
  • Twitter (as well as documentation)
    https://api.twitter.com/1/account/verify_credentials.json?oauth_access_token={token-to-inspect}

If the token is valid, we compare the ID extracted from the Authorization header with the one specified in the URL. If any of the above two fail we return a 401 Unauthorized response to the client. If we pass those two checks, we do a lookup in our user database to find the user with the specified Social Media ID (step 5. in the diagram) and retrieve her record. We also retrieve information about her group participation so that we can do authorization later on for each one of the functional calls. If we cannot find the user in our database we return a 404 Not found response to the client.

Create API Token

For the purposes of our APIs, we decided to use encrypted JWT tokens. We include the following information into the JWT token:

  • User information like ID, first name and last name, email, address, city, state, zip code
  • Group membership for the user including roles
  • The authentication token for the Social Media service the user authenticated with
  • Expiration time (we settled on 60 minutes expiration)

Before we send this information back to the client (step. 8 in the diagram) we encrypt it (step. 7) using an encryption key or secret that we keep in Azure Key Vault (step. 6). The JWT token is sent back to the client in the Authorization header.

Call the functional APIs

Now, we replaced the access token the client received from the Social Media site with a JWT token that our application can understand and use for authentication and authorization purposes. Each request to the functional APIs (step 9 in the diagram) is required to have the JWT token in the Authorization header. Each API handler has access to the encryption key that is used to decrypt the token and extract the information from it (step 10).

Here are the checks we do before  every request is handled (step 11):

  • If the token is missing we return 401 Unauthorized to the client
  • If the user ID in the URL doesn’t match the user ID stored in the JWT token we return 401 Unauthorized to the client. All API requests for our product are executed in the context of the user
  • If the JWT token has expired we return 401 Unauthorized to the client. For now, we decided to expire the JWT token every 60 mins and request the client to re-authenticate with the APIs. In the future, we may decide to extend the token for another 60 mins or until the Social Media access token expires, so that we can avoid the user dissatisfaction from frequent logins. Hence we overdesigned the JWT token to store the Social Media access token also
  • If the user has no right to perform certain operation we return 403 Forbidden to the client denoting that the operation is forbidden for this user

Few notes on the implementation. Because we use Python we can easily implement all the authentication and authorization checks using decorators, which make our API handlers much easier to read and also enable an easy extension in the future (like for example, extending the validity of the JWT token). Python has also an easy to use JWT library available at Github at https://github.com/jpadilla/pyjwt.

Some additional resources that you may find useful when implementing JWT web tokens are:

 

Over the last few days, I was looking for a way to automate our deployment environments on Azure and also investigating automation frameworks for a customer. The debate was between Terraform and Ansible and the following article from Gruntwork did really good work on tilting the weight towards Terraform. We have similar considerations like the guys from Gruntwork so everything matched well. Now, the task was to get Terraform working with Azure, which was a small challenge compared to AWS.

For those of you interested in the background here is the Terraform documentation for Azure provider, which is pretty good but missed a small piece about assigning a role as described in this StackOverflow post. Of course, the post points to the Azure documentation about using the CLI to assign the role to the principal, which for me ended up with the following error:

Principals of type Application cannot validly be used in role assignments.

At the end of the day because of time pressure, I wasn’t able to figure out the CLI way to do that but it seems there is a way to do it through the Azure Management Portal so here are the steps with visuals:

Create Application Registration in Your Azure Subscription

  • Go to the new Azure Portal at http://portal.azure.com and select Azure Active Directory in the navigation pane on the left:

  • Select App Registrations from the tasks blade

  • Click on the Add button at the top of the blade and fill in the information for the Terraform app. You can choose any name for the Name field as well as any valid URL string for the Sign-on URL field. Click on the Create button to create the app.

  • Click on the newly created app and in the Settings blade select Required Permissions

  • Click on the Add button at the top of the blade

  • In Step 1 Select an API select the Windows Azure Service Management API and click on the Select button

  • In Step 2 Select Permissions select Access Azure Service Management as organization users (preview) and click on the Select button

  • Click on the Done button to complete the flow

Now, you have your App Registration complete however you still need to assign a role for your application. Here is how this is done.

Assign a Role for Terraform App to Use ARM

Assigning role to your application is done on a Subscription level in Azure Portal.

  • Select Subscriptions in the navigation pane on the left

  • Select the subscription where you have registered the app and select Access Control (IAM) in the task blade

  • Click on the Add button at the top of the blade and in Step 1 Select a role choose the most appropriate role for your Terraform application

Although you may be tempted to choose Owner in this step I would suggest thinking your security policies through and selecting role that has more restrictive access. For example, if you have DevOps people running Terraform scripts you may want to give them Contributor role and prevent them from managing the user access. Also, if you have database team that wants to only manage Azure SQL and DocumentDB you may just restrict them to SQL DB Contributor and DocumentDB Account Contributor. List of built-in RBAC roles for Azure is available here.

  • In Step 2 Add Users type the name of your app in the search field and select it from  the list. Click on the Select button to confirm

  • Click the OK button to complete the flow

Collecting ARM Credentials Information for Terraform

In order for Terraform to connect to Azure and manage the resources using Azure Resource Manager you need to collect the following information:

  • Subscription ID
  • Client ID is also known as Application ID in Azure terminology
  • Client Secret is also known as Key in Azure terminology
  • and Tenant ID is also known as Directory ID in Azure terminology

Here is where to obtain this information from.

Azure Subscription ID

Click on Subscriptions in the navigation pane -> Select the subscription where you created the Terraform app and copy the GUID highlighted in the picture below.

Azure Client ID

What Terraform refers to as Client ID is actually the Application ID for the app that you just registered. You can get it by selecting Azure Active Directory -> App registrations -> select the name of the app you just registered and copy the GUID highlighted in the picture below.

Azure Client Secret

What Terraform refers to as Azure Client Secret is a Key that you create in your App registration. Follow these steps to create the key:

  • From Azure Active Directory -> App registrations select the application that you just created and then select Keys in the Settings blade

  • Fill in the Key description, select Duration and click on the Save button at the top of the blade. The Key value will be shown after you click the Save button.

Note: Copy and save the key value immediately. If you navigate away from the blade you will not be able to see the value anymore. You can delete the key and create a new one in the future if you lose the value.

Azure Tenant ID

The last piece of information you will need to connect Terraform to Azure Resource Manager is a Tenant ID, which is also known as Directory ID in Azure terminology. This is actually the GUID used to identify your Azure Active Directory.

Select Azure Active Directory and scroll down to show the Properties in the tasks blade. Select Properties and copy the GUID highlighted on the picture below.

Terraform documentation describes a different method to obtain the Tenant ID that involves showing the OAuth Authorization Endpoint for the application that you just created and copying the GUID from the URL. I think, their approach is a bit more error prone but if you feel comfortable in your Copy/Paste abilities you may want to give it a try.

I hope that by describing this a bit convoluted registration process you will be able to be more productive managing your resources on Azure.