Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samlandfried/bc5375156d7d56c0248520527bdd7434 to your computer and use it in GitHub Desktop.
Save samlandfried/bc5375156d7d56c0248520527bdd7434 to your computer and use it in GitHub Desktop.
Learn how to retrieve a user's access token to an identity provider with Auth0

Auth0 and ID Provider Access

How to Get API Access Tokens to Services You Authenticate With

What?

Auth0 is an incredible tool with a wide range of capabilities for anyone building a web app that relies on storing and authenticating users. It's the AWS of user management via oauth (Although I imagine AWS has some service that takes care of that). The documentation on Auth0 is similarly comprehensive. I found a homegrown tutorial on their site that covered everything I needed to do (When you create a new app, you answer a few questions to describe the type of app you're building and what technology you're using, and it serves the exact tut you need). Even with Auth0 pushing the limit of what a service can do to make it easy for their users to dive in, I spent a solid 2 days trying to get everything working together. Hopefully, this blog can help you clear one hurdle quicker than me.

What I won't talk about

I'm not going to talk about how to authenticate users with Auth0. That's step 1, and it really comes down to copy and pasting the code they provide.

What I will talk about

Once you are successfully authenticating users against an external oauth provider (I'll use Slack in this example), you'll notice that Auth0 happily provides you with that user's credentials for Auth0, NOT the service you authenticated against. This is problematic if you need to communicate with the authenticating service's API. That's where I'll help out. I'm going to describe the process of retrieving the access token for services you use to authenticate your user.

In visual form

To get a web app running that authenticates users against an oauth provider AND makes calls to that oauth provider's API, these are the steps required, and the ones I'll describe are in bold:

I'm using Slack as the oauth provider

  1. Create a new Auth0 client
  2. Create a new Slack app
  3. Enable Slack as a custom social extensions in Auth0
  4. Plug Slack credentials into the Auth0 client
  5. Specify valid callback URLs in Auth0 client and Slack
  6. Setup authing in your app (Just a matter of copy and pasting for my React.js frontend, and it looks equally straightforward with dozens of other tech stacks)
  7. Create an Auth0 Management API account
  8. Fetch your Auth0 Management API token from your app
  9. Fetch Auth0 user information from the Auth0 Management API from your app
  10. Parse out oauth Provider access token
  11. Profit

Brief rundown of the process

To get an access token to our oauth provider (AKA Identity Provider AKA PID) we need to retrieve it from Auth0's Management API. To do that, we need an access token for that API. So, we're going to get two separate access tokens for two separate APIs.

This is Auth0's explanation of the wole process (Spread over several articles)

First: Setup access to Auth0's Management API

Here's Auth0's explanation on how to do it.

  1. Create a client to interface with the API

Go here and enable the client with all default settings

You'll need the ID and Secret, so make sure you know how to access them. Get your Auth0 client secret and id I store mine in a .gitignoreed file that I reference as AUTH_CONFIG in the code below.

  1. Setup programmatic Auth0 Management API access token retrieval

The Auth0 tutorial tells you how to do it manually, but that's not useful for our purposes since we'll have to regularly retrieve new tokens. They also describe the automated process further down the page. Here's my React method that fetches that token:

function() getManagementToken {
    const body = {
      client_id: AUTH_CONFIG.managementId,
      client_secret: AUTH_CONFIG.managementSecret,
      audience: "https://samlandfried.auth0.com/api/v2/",
      grant_type: "client_credentials"
    };
    const options = {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body)
    };

    fetch("https://samlandfried.auth0.com/oauth/token", options)
      .then(
        function(resp) {
          return resp.text();
        }.bind(this)
      )
      .then(
        data => {
          const parsed = JSON.parse(data);
          const mgmtToken = parsed.access_token;
          // Trigger API call to get user identities (Including Slack access token)
        }
      )
  }
  1. Use token to retrieve currently authed user identities Here are the official docs for the Auth0 Management API.

What we want to do is make a call to the https://samlandfried.auth0.com/api/v2/users/{AUTH0_USER_ID} endpoint with the token retrieved from the last step to authorize the request. Here's how I did it. This code replaces the insides of the final then block in the code above. At the bottom I'll include the entire snippet. Oh. You might be asking, "Where do we get the Auth0 User ID of the currently logged in user?" Well, if you followed the Auth0 tutorials to authenticate your users, your browser should have a local item stored called id_token. It contains the user's ID. It's a JWT, so you'll need a JWT decoding library to access it. I like jwt-decode.

function(data) {
  const parser = require("jwt-decode");
  const u_id = parser(localStorage.id_token)["sub"];
  const parsed = JSON.parse(data);

  const options = {
    method: "GET",
    headers: {
      "content-type": "application/json",
      Authorization: "Bearer " + parsed.access_token
    }
  };
  // get user profile by parsing the localStorage.id_token JWT
  return fetch("https://samlandfried.auth0.com/api/v2/users/" + u_id, options);
}.bind(this)
.then(
function(resp) {
  return resp.json();
}.bind(this)
)
.then(
function(data) {
  this.setState({ user: data });
}.bind(this)
);

2 interesting things to point out that I hadn't seen before:

  1. The JWT is passed back to the Auth0 Management API without being parsed in an Authorization header.
  2. The function is returning the result of fetch, which is a promise, so we can keep chaining then on the end of the anonymous function.

Since I'm writing a React app, I update the state with the data which is a nice object with all sorts of info about the user, including the access token. Here's what data in that last block should look similar to:

{
    "name":"Sam Landfried",
    "email":"samlandfried@gmail.com",
    "image_24":"https://avatars.slack-edge.com/5f8bc3c6aac65598128_24.jpg",
    "image_32":"https://avatars.slack-edge.com/5f8bc3c6aac65598128_32.jpg",
    "slack_id":"U4ABV556N",
    "team":{
        "id":"T4BBV445Q",
        "name":"lunchers",
        "domain":"luncherrollers",
        "image_34":"https://a.slack-edge.com/66f9/img/avatars-teams/ava_0010-34.png",
        "image_44":"https://a.slack-edge.com/66f9/img/avatars-teams/ava_0010-44.png",
        "image_default":true
    },
    "updated_at":"2017-07-23T17:05:55.371Z",
    "user_id":"oauth2|slack|U4ABV556N",
    "nickname":"samlandfried",
    "identities":[
        {
            "provider":"oauth2",
            "access_token":"Wouldnt you like to know",
            "user_id":"slack|U4ABV556N",
            "connection":"slack",
            "isSocial":true
        }
    ],
    "created_at":"2017-07-10T20:46:13.499Z",
    "last_login":"2017-07-23T17:05:55.370Z",
    "logins_count":88
}

You'll see that the access token you need is nested under the identities key of the object. Now go ping that API! And here is the entire nasty function. Lemme know if you refactor this into something more tolerable!

componentDidMount() {
    const body = {
      client_id: AUTH_CONFIG.managementId,
      client_secret: AUTH_CONFIG.managementSecret,
      audience: "https://samlandfried.auth0.com/api/v2/",
      grant_type: "client_credentials"
    };
    const options = {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body)
    };

    fetch("https://samlandfried.auth0.com/oauth/token", options)
      .then(
        function(resp) {
          return resp.text();
        }.bind(this)
      )
      .then(
        function(data) {
          debugger
          const parser = require("jwt-decode");
          const u_id = parser(localStorage.id_token)["sub"];
          const parsed = JSON.parse(data);

          const options = {
            method: "GET",
            headers: {
              "content-type": "application/json",
              Authorization: "Bearer " + parsed.access_token
            }
          };
          // get user profile by parsing the localStorage.id_token JWT
          return fetch(
            "https://samlandfried.auth0.com/api/v2/users/" + u_id,
            options
          );
        }.bind(this)
      )
      .then(
        function(resp) {
          return resp.json();
        }.bind(this)
      )
      .then(
        function(data) {
          debugger
          this.setState({ user: data });
        }.bind(this)
      );
    } 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment