Lab: OAuth 2.0

OAuth 2.0 is a protocol that allows a user to grant a third-party web site or application access to the user’s protected resources, without necessarily revealing their long-term credentials or even their identity. For example, a user could allow a social media site to access their photos without sharing their full access to their account.

Overview

In this lab, you will learn how to use OAuth 2.0 to authenticate users, authorize access to resources, obtain an access token, and refresh an access token. We will use Express and Vue.js as platforms to demonstrate how we could connect to a third-party OAuth 2.0 provider.

Objectives

In this lab, you will learn how to:

  • Use OAuth 2.0 to authenticate users
  • Use OAuth 2.0 to authorize access to resources
  • Obtain an access token
  • Refresh an access token
  • Use OAuth 2.0 with Express
  • Use OAuth 2.0 with Vue.js

Prerequisites

  • Basic knowledge of JavaScript
  • Basic knowledge of Express
  • Basic knowledge of Vue.js
  • Basic knowledge of RESTful APIs
  • Basic knowledge of HTTP
  • Basic knowledge of JSON
  • Basic knowledge of Web development

OAuth 2.0 Authentication flow

In order to implement OAuth 2.0, you need to understand the basic authentication flow.

Overview

Let’s take a look at the basic OAuth 2.0 authentication flow. The following diagram shows the sequence of steps involved in the OAuth 2.0 protocol:

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    participant API Resource Server
    User->>Client: Access
    Client->>External Authorization Server: Authorization Request
    External Authorization Server->>User: Authorization Prompt
    User->>External Authorization Server: Authorization Grant
    External Authorization Server->>Client: Authorization Code
    Client->>External Authorization Server: Token Request
    External Authorization Server->>Client: Access Token
    Client->>API Resource Server: Access Token
    API Resource Server->>Client: Protected API Resource

Client is the application that wants to access the user’s protected resources. External Authorization Server is the server that authenticates the user and issues an access token to the client. API Resource Server is the server that hosts the user’s protected resources.

Step 1: Authorization Request

When User wants to access the Client, it will redirect the user to the External Authorization Server to authenticate the user.

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    User->>Client: Access
    Client->>External Authorization Server: Authorization Request
    External Authorization Server->>User: Authorization Prompt
    User->>External Authorization Server: Authorization Grant
    External Authorization Server->>Client: Authorization Code

After the user is authenticated, the External Authorization Server will send an Authorization Code to the Client. The Client will then exchange the Authorization Code for an Access Token from the External Authorization Server.

Step 2: Token Request

After the Client receives the Authorization Code, it will send a Token Request to the External Authorization Server to obtain an Access Token.

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    User->>External Authorization Server: Authorization Grant
    External Authorization Server->>Client: Authorization Code
    Client->>External Authorization Server: Token Request
    External Authorization Server->>Client: Access Token

Step 3: Access Token

After the Client receives the Access Token, it will use the Access Token to access the API Resource Server.

sequenceDiagram
    participant Client
    participant API Resource Server
    Client->>API Resource Server: Access Token
    API Resource Server->>Client: Protected API Resource

Implementing OAuth 2.0 in Express and Vue.js

In this lab, we will use Auth0 to demonstrate how to implement OAuth 2.0 in Express and Vue.js. Auth0 is a flexible, drop-in solution to add authentication and authorization services to applications and APIs. Auth0 supports the latest OAuth 2.0 and OpenID Connect (OIDC) specifications. OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol, which allows clients to verify the identity of the end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user in an interoperable and REST-like manner. In this lab, we will use Auth0 to demonstrate how to implement OAuth 2.0 in Express and Vue.js.

Application setup

In order to simply the setup process, we have already registered an application with Auth0. The application has the following details:

  • Domain: hkbu.jp.auth0.com
  • Client ID: dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw
  • Client Secret: AGu90xbpSYfM22Jd3RUHvXL1esG2e_m8y3yfvdm0LHHuP1WMxj7ZX9DrMGwU1879
  • Callback URL: http://localhost:3000/callback
  • Logout URL: http://localhost:3000

Login

The first step is to redirect the user to the authorize endpoint to login the user with OAuth 2.0.

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    User->>Client: Access
    Client->>External Authorization Server: Authorization Request

To login the user with OAuth 2.0, simply redirect the user to the authorize endpoint with the required parameters. The required parameters are:

  • response_type: The type of response that the client expects. In this case, it is code.
  • client_id: The client ID of the application.
  • redirect_uri: The URL to redirect the user after the authentication is successful.

There are also optional parameters:

  • scope: The scope of the access request. In this case, it is openid profile email.
  • state: A random value generated by the client to maintain state between the request and the callback.

The following is an HTML example of how to login the user with OAuth 2.0. You may add it to the App.vue of your Vue.js application to login the user with OAuth 2.0.

<a href="https://hkbu.jp.auth0.com/authorize?response_type=code&client_id=dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback">Login</a>

Or, you may use JavaScript to redirect the user to the authorize endpoint.

import { v4 as uuidv4 } from 'uuid';

location.assign('https://hkbu.jp.auth0.com/authorize?response_type=code&client_id=dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback')

If you are doing in the backend, you may use the following code to redirect the user to the authorize endpoint. In this case, we could easily to inject a random state value for identifying the user.

const { v4: uuidv4 } = require('uuid');

const state = uuidv4()

res.redirect(`https://hkbu.jp.auth0.com/authorize?response_type=code&client_id=dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw&state=${state}redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback`)

The External Authorization Server will then authenticate the user.

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    User->>Client: Access
    Client->>External Authorization Server: Authorization Request
    External Authorization Server->>User: Authorization Prompt
    User->>External Authorization Server: Authorization Grant

Callback

After the user is authenticated, the External Authorization Server will redirect the user back to the redirect_uri with an Authorization Code. The redirect_uri should be the URL on the backend server of Client.

sequenceDiagram
    participant User
    participant Client
    participant External Authorization Server
    User->>Client: Access
    Client->>External Authorization Server: Authorization Request
    External Authorization Server->>User: Authorization Prompt
    User->>External Authorization Server: Authorization Grant
    External Authorization Server->>Client: Authorization Code
    Client->>External Authorization Server: Token Request
    External Authorization Server->>Client: Access Token

The Authorization Code is a short-lived token that the Client can exchange for an Access Token. The Client will then exchange the Authorization Code for an Access Token from the External Authorization Server.

In order to receive the Authorization Code in the backend, you need to create a route to handle the redirect_uri. It receives the Authorization Code from the External Authorization Server with query parameters, and then exchanges the Authorization Code for an Access Token with POST request using fetch API. Here is an example of how to handle the redirect_uri in Express.

router.get('/callback', async (req, res) => {
    const code = req.query.code
    if (!code) {
        return res.status(400).send('Bad Request')
    }
    const response = await fetch('https://hkbu.jp.auth0.com/oauth/token', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            grant_type: 'authorization_code',
            client_id: 'dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw',
            client_secret: 'AGu90xbpSYfM22Jd3RUHvXL1esG2e_m8y3yfvdm0LHHuP1WMxj7ZX9DrMGwU1879',
            code: code,
            redirect_uri: 'http://localhost:3000/callback',
            scope: 'openid profile email'
        })
    })
    if (!response.ok) {
        return res.status(400).send('Bad Request')
    }
    const data = await response.json()
})

The above data should be like:

{
   "access_token":"eyJz93a...k4laUWw",
   "refresh_token":"GEbRxBN...edjnXbL",
   "id_token":"eyJ0XAi...4faeEoQ",
   "token_type":"Bearer",
   "expires_in":86400
}

After that, we may use the Access Token to access the APIs provided by External Authorization Server.

The integration with Express and Vue.js

Let’s take a look at how to integrate OAuth 2.0 with Express and Vue.js. The Client in original diagram now is separated into two parts: Vue.js and Express. Vue.js is the frontend application that redirects the user to the authorize endpoint to login the user with OAuth 2.0. Express is the backend server that receives the Authorization Code from the External Authorization Server with query parameters, and then exchanges the Authorization Code for an Access Token with POST request using fetch API.

sequenceDiagram
    participant User
    participant Vue.js
    participant Express
    participant External Authorization Server
    User->>Vue.js: Access
    Vue.js->>External Authorization Server: Authorization Request
    External Authorization Server->>User: Authorization Prompt
    User->>External Authorization Server: Authorization Grant
    External Authorization Server->>Express: Authorization Code
    Express->>External Authorization Server: Token Request
    External Authorization Server->>Express: Access Token
    Express->>Vue.js: JWT Token
    Vue.js->>Express: use JWT Token to access protected resources

The following is an example of how to handle the redirect_uri in Express. Please note that fetch API is only supported by nodejs v18+.

router.get('/callback', async (req, res) => {
    const code = req.query.code
    if (!code) {
        return res.status(400).send('Bad Request')
    }
    
    // Exchange the code for an access token
    try {
        const response = await fetch('https://hkbu.jp.auth0.com/oauth/token', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              grant_type: 'authorization_code',
              client_id: 'dxiOZbTWeGFhW4ScsQrV6rpR1IWO8VJw',
              client_secret: 'AGu90xbpSYfM22Jd3RUHvXL1esG2e_m8y3yfvdm0LHHuP1WMxj7ZX9DrMGwU1879',
              code: code,
              redirect_uri: 'http://localhost:3000/callback',
              scope: 'openid profile email'
            })
        })
        if (!response.ok) {
            return res.status(400).send('Bad Request')
        }
        const data = await response.json()
        // the response should be like
        // {
        //     "access_token":"eyJz93a...k4laUWw",
        //     "refresh_token":"GEbRxBN...edjnXbL",
        //     "id_token":"eyJ0XAi...4faeEoQ",
        //     "token_type":"Bearer",
        //     "expires_in":86400
        // }
        // we may fetch the user profile with the access token
        // ...
        // after that, generate a jwt token and send it to frontend for authentication
        const token = jwt.sign(data, 'secret')
        return res.redirect(`http://localhost:5173/login-success?token=${token}`)
    } catch (error) {
        console.error(error)
        return res.status(500).send('Internal Server Error')
    }
})

After receiving the Access Token, we may fetch the user profile with the Access Token from the External Authorization Server. After that, we may generate a JWT token and send it to the frontend for authentication.

If we would like to include user profile information in the JWT token, we may fetch the user profile with the Access Token from the External Authorization Server. Auth0 provides a /userinfo endpoint to fetch the user profile with the Access Token. You may check out the documentation of Auth0 for more details.

Here is an example of how to fetch the user profile with the Access Token in Express.

const response = await fetch('https://hkbu.jp.auth0.com/userinfo', {
    headers: {
        'Authorization': `Bearer ${data.access_token}`
    }
})
if (!response.ok) {
    return res.status(400).send('Bad Request')
}
const profile = await response.json()