Pre-reading

What is SSO/SAML?

TL;DR: SAML is oAuth, but for enterprise companies. If I am an employee at FANGA.com, I would use SAML to login to Workday.com to view my pay stubs.

What is a JWT (Json Web Token)?

Problem

Design an authentication service that builds upon an existing email / password service by adding oAuth (Facebook, Google, 3rd party), and SSO (SAML) and is multi-tenant (groups of users under one organization).

We need an admin web interface for managing the authentication configurations (which organizations support what types of authentication) and how to connect with the org’s identity provider (in the case of SAML).

We also have a large number of micro-services running Hasura, Google Firebase, and even Mailchimp that we need to authenticate the user with. These microservices will be controlled via Single Page Apps (React, Angular, etc.) and even mobile apps (maybe running Facebook’s React Native) Each of these services need to know the user’s basic information (which tenant they belong to, their email, their user-id, and their name).

How would you design such a service?

Feature list

  • SAML to React Native
  • oAuth to Gatsby + Hasura
  • SAML to AngularJS + Firebase

Solution

Json Web Tokens are a perfect solution for this type of problem because they allow us to securely share verified information across service. We can translate OpenID and SAML data to a standard JWT payload which can be shared across all of the microservices. Each microservice can verify and access all of the key consumer-identifying attributes (like org_id or consumer_id).

The JWT token can be used for registration with existing user management systems (like Devise or Django). The existing service will verify and extra the email address and other attributes from the JWT payload and persist them alongside their username and password registration tooling.

Part 1: Admin Web interface

Let us start with our data models:

Users

  • org_id (int)
  • Name (string)
  • Email (string)
  • Role (string)

Organizations

  • Name (string)
  • Logo (string)
  • JWT encryption_secret (string)

OrganizationDomains

  • org_id (int)
  • Domain_name (string)

AuthenticationConfigurations

  • Type (string)
  • Configuration (json)
  • org_id (int)
  • redirect_url

As a User, I can log in to the admin dashboard (https://auth.service.com/admin) and configure what types of authentication I want to support my organization. This user account is not an end-user (consumer) that will be consuming my micro-services (Hasura, Firebase, etc). We will need to be careful about storing consumer information because we can only persist their data after they have accepted our Terms of Service and Privacy Policy to stay GDPR compliant.

The AuthenticationConfiguation model will help us store our SAML or OAuth configurations so we will know how to authenticate each consumer.

Part 2: Service Authentication

Next, we need to discuss the consumer experience: what happens when they want to login to your website?

SAML to JWT

SAML: Identity Provider Authentication to JWT

When the consumer accesses their identity provider’s web portal, they will have the option to choose which service they want to authenticate with. When they click on the service, an XML file is passed to our auth service with pre-selected attributes (email, name, etc.) and a signed certificate which we will verify based on the org-id at the URL they are requesting access to (e.g. https://auth.service.com/org_id/{id}). If the user is valid, we will redirect them to the respective service’s endpoint which can hangle registering them (if they have not been registered before or logging them in). More details about that to come.

SAML: Service Provider Authentication to JWT

When the user accesses your service’s website (e.g. https://app1.service.com) or React Native app, they will be asked for their email address. The service provider will send a GET request, asking auth.service.com/email={email} if the email address is to be authenticated with SSO/SAML, oAuth, or Email/Password. If the consumer is authenticated with the prior, they will be redirected to https://auth.service.com to verify their identity.

When the oAuth or SSO provider returns with a valid payload, we generate a JWT token and redirect them back to https://app1.service.com

JWT Payload

{
  "org_id": 123,
  "email": "[email protected]",
  "full_name": "George P Burdell",
  "employee_number": 555,
  "manager_email": "[email protected]",
  "job_title": "Director",
  "letter_avatar": ".../KC.jpg"
}

With OAuth (Google or Facebook or OpenID), we will be able to collect a set of attributes about our consumer and can wrap them together into the JWT token which can be shared with our micro-services. SAML also supports sharing additional features with our authentication service like their employee id, their email address, or letter avatar.

Part 3: Integrate with services

Integration with auth.service.com will be easy. Consuming services will need to verify the JWT using any of the available JWT libraries for their respective framework, or if you use Hasura, this will work out of the box. Once the token has been verified, the service can log in to the account and return their existing authentication token or if the user needs to be registered, ask the user to consent to their Terms of Service and Privacy Policy before persisting their information.

class User < ActiveRecord::Base
  validate  :verify_registration_token, if: :registration_token

  def extract_registration_token
    payload = jwt_payload(verify: false)
    self.name = payload["name"]
    self.email = payload["email"]
    self.password = SecureRandom.hex(20)
  end

  def jwt_payload(verify: true)
    JWT.decode(registration_token,
               Doorkeeper::JWT_SECRET,
               verify,
               algorithm: "HS256").first
  end

  def verify_registration_token
    jwt_payload(verify: true)
  end
end

In this sample Ruby code, you can see how the downstream service users the JWT registration token to create the user’s account.

Design Tradeoffs

Pros

  • Works with existing username/password Auth libraries (Devise, Django, Go Auth, etc.)
  • GDPR Compliant. auth.service.com never stores PII.
  • Scales Globally. This can run on globally distributed lambda functions (like Cloudflare workers) allowing less than 15ms response times.

Cons

  • Does not support Email / Password, (or any of the related nice-ities like password recovery), but it can be added!

Want to use it?

Great! Because we are building it! Shoot me an email if you want to test drive it:

[email protected]