Taking the fear out of authorization: OAuth essentials for frontenders

Explore the essentials of OAuth2.0, and how to take your web applications to the next level by removing the complexities around integrating 3rd party REST APIs in frontend development.

Image description

OAuth2.0 is one of the most commonly dreaded authorization methods to implement in frontend development - and understandably so! With its various flows, exchanges, and other intricacies, it can be overwhelming. But, it's also an essential authorization flow that opens the door to a seemingly unlimited number of 3rd party use cases.

Let's take the fear out of OAuth2.0!

  1. Understand OAuth's role in frontend development

  2. Explore common flows, or grant types

  3. Master the authorization code flow

OAuth2.0 isn't just for backenders

If you're a frontend developer, you've most likely encountered OAuth2.0 in some capacity, but perhaps you've never needed to fully implement it in your own applications, or it was handled separately in backend development.

But actually, OAuth2.0 is an essential authorization flow for all types of developers. And by equipping yourself with the context and confidence necessary to implement it all on your own, you open up the door to new capabilities to complement the native ones you've already built. Not to mention, there might come a time, even in frontend development, when you're tasked to incorporate some of your end users' favorite tools like Miro, Google, Zoom, and more.

So, let's remove some of the complexities that can cause us to avoid OAuth2.0 in the first place.

OAuth2.0 flows in a nutshell

OAuth2.0 supports various different "flows", or grant types, as you'll see them officially referenced. These are essentially just different ways of getting to the same end-goal: receiving authorization to work with some type of resource (via an API).

Image description

Source: OAuth.io

However, there are many different flows, and they apply to a variety of different use cases or implementations. You can find the full list and explanation of each flow on OAuth.net, but let’s cover a couple of the more common ones now.

Authorization Code

The authorization code flow is one of the most common flows, especially in frontend development, where it’s expected that an end-user will provide their authorization in some sort of frontend UI.

PKCE

PKCE is an extension for the authorization code flow, originally intended for mobile development. However, it was found to be an effective mechanism for all mediums, and is used to prevent CSRF and authorization code injection attacks.

Implicit Grant

You may see this referenced, but it’s a legacy flow, and no longer a best practice/recommended grant type to leverage. It was found to be vulnerable to bad actors/risks upon returning an access token directly in an HTTP redirect (without any kind of exchange like in the authorization code flow).

Mastering the authorization code flow

Despite being one of the most popular implementations of OAuth2.0, the authorization code flow can seem daunting at first, given the number of steps. But once it’s broken down a bit, things become much simpler. There are 5 main steps in this flow:

  1. End user authorization

  2. Exchange of the authorization code

  3. Retrieval of an access token

  4. Request to the resource server

  5. Refresh of the access token

Let’s lay things out in layman’s terms and use some sample code snippets from a sample app we’ve built in NextJS.

First, a user of your application will need to provide their consent, or authorize your application to access data on their behalf. This is step 1, and occurs when the end user is directed to the authorization URL that your app constructs, based on the resource owner (e.g., Miro, Google, etc.).

Image description

Here’s an example function, where the two environment variables included are: a Miro-provided Client ID, and a specified callback URL, respectively:

export default function handler(req, res) {
 res.redirect(
 "https://miro.com/oauth/authorize?response_type=code&client_id=" +
 process.env.clientID +
 "&redirect_uri=" +
 process.env.redirectURL
 );
}

(You can find a more detailed explanation of constructing this URL in Miro’s OAuth2.0 starter guide as well.)

Next, your application will need to receive a code parameter from the redirect url, immediately following the end user’s consent, and exchange this for an access_token — this all happens on your app’s backend/server. This is steps 2 and 3, respectively:

import axios from "axios";

export default function handler(req, res) {
  let access_token;
  let refresh_token;

  if (req.query.code) {
    let url = `https://api.miro.com/v1/oauth/token?grant_type=authorization_code&client_id=${process.env.clientID}&client_secret=${process.env.clientSecret}&code=${req.query.code}&redirect_uri=${process.env.redirectURL}`;

    async function grabToken() {
      try {
        let oauthResponse = await axios.post(url);

        access_token = oauthResponse.data.access_token;
        refresh_token = oauthResponse.data.refresh_token;

        if (access_token) {
          console.log("access_token = " + access_token)
          console.log("refresh_token = " + refresh_token)

// {{ Some code here to store or leverage this access token }}

          res.redirect("/");
        }
      } catch (err) {
        console.log(`ERROR: ${err}`);
      }
    }
    return grabToken();
  }
}

After this, your app can leverage this access_token you retrieved in the previous step, and use it as the value of the bearer token that your app will authorize any subsequent API requests to (for the particular resource the access token belongs to — e.g., Miro REST API). This is step 4. Here is an example of a function that makes a request to the Miro REST API, using the access_token retrieved in the last step:

import axios from "axios";

export default function handler(req, res) {
  const headers = {
    Accept: "application/json",
    "Content-Type": "application/json",
    Authorization: `Bearer ${access_token}`,
  };

  axios
    .get(
      "https://api.miro.com/v2/boards",
      {
        headers: headers,
      }
    )
    .then(function (response) {
      res.json(response.data);
    })
    .catch(function (error) {
      console.log(error);
    });
}

And lastly, it’s important to note that access tokens aren’t valid forever (technically, they can be — but this is not a best practice, and it’s considered a security vulnerability to have non-expiring access tokens). A typical access_token is valid for some amount of time — usually 60 minutes — and then a new access token should be requested, using a refresh_token. (A refresh_token is typically returned alongside your original access_token in step 3, depending on your resource owner’s configuration settings.).

While it’s admittedly a few steps end-to-end, it helps to visualize things a bit. Here’s a high-level overview of each of these 5 steps, and their interactions between the end user, an application, and the resource owner (again, this could be Miro, Google, etc.).

Image description

Master OAuth2.0 with Miro

Interested in going deeper with OAuth2.0? Check out this on-demand educational training series, OAuth Essentials for Frontenders: youtube.com/watch?v=najU71JaATQ

And keep going with OAuth2.0, leveraging the Miro Developer Platform’s assortment of sample apps!

You can even create a developer team and start quickly calling Miro’s REST APIs directly from our documentation. You can find the full Getting Started flow here.

Let us know what you build! We’re always happy to talk shop with fellow developers. 🙂

Did you like learning more about OAuth2.0 and how to leverage the Miro Developer Platform? For more inspiration or questions, follow along with our Developer Community on YouTube, GitHub, and Discord.

Originally published at dev.to on December 12, 2022.