Generate personal access tokens with Netlify OAuth

by Domitrius Clark

Every action available in the Netlify UI is powered by an API endpoint. This means you can integrate these same capabilities directly into your own tools and automation workflows.

We’re going to walk through a process that generates the access token you’ll need to act on behalf of your users to do things like:

  • Engage in CI/CD flows for your users
  • Manage account and user information
  • Take actions on behalf of your users

With these actions comes the risk of bad actors circumventing your access and using it nefariously. While showing you this workflow, we’ll also work through building safeguards to help protect you and your users.

#What are we building?

This is going to be a simple static site where:

  1. We create an OAuth app that determines the redirect URL.
  2. We craft and visit a URL with our client id, our redirect URL for the user to approve, and our generated state.
  3. After approving, Netlify redirects to the redirect URL with the token in the URL
  4. This token will be used when making calls to the API on behalf of the user.
  5. We then use this token to call a Netlify API and generate a Personal Access Token.

#Create an OAuth application

Before we dive into the code, we need to create a new OAuth application. From the Netlify Dashboard navigate to your “User Settings” and from there to Applications.

Select “New OAuth app” and fill in any details for your project. For this guide we’ve filled it in as follows:

The form presented when creating a new OAuth application in the Netlify settings

Save your Client ID and Secet key somewhere safe. You’ll be using the Client ID in your app.js file in the next section.

With that setup, we can move into the code.

#Create and setup the application

Since we’re not going to use any frameworks, getting set up is as easy as creating a folder with two files: app.js and index.html in your project directory.

First, let’s set up our HTML so we have elements we can utilize in our js file.

index.html
<!doctype html>
<html>
<head>
<title>Netlify OAuth & Personal Access Token Generation</title>
</head>
<body>
<h1>Netlify OAuth & PAT Generation</h1>
<button id="oauth-button">Start OAuth Flow</button>
<script src="/app.js"></script>
</body>
</html>

Next, we’ll fill in our app file:

app.js
// Input your CLIENT_ID here -- this ID fine to be public and is needed to craft our OAuth URL
const CLIENT_ID = "";
const REDIRECT_URI = "http://localhost:8888/";
// Simple and secure way to generate state. This state is used to help us prevent CSRF attacks while moving through the OAuth flow
function generateState() {
return crypto.randomUUID();
}
function handleNetlifyOAuth() {
const clientId = CLIENT_ID;
const redirectUri = REDIRECT_URI;
const state = generateState();
// Setting our state enables us to use the value to check the validity of the requester
localStorage.setItem('oauth_state', state);
const authLink = `https://app.netlify.com/authorize?client_id=${clientId}&response_type=token&redirect_uri=${redirectUri}&state=${state}`;
window.location.href = authLink;
}
document.addEventListener('DOMContentLoaded', () => {
const oAuthButton = document.querySelector("#oauth-button");
if (oAuthButton) {
oAuthButton.addEventListener("click", handleNetlifyOAuth);
} else {
console.error('OAuth button not found');
}
});
const hash = document.location.hash;
/*
* This function is called when a user returns from Netlify and has accepted the
* request to authorize your app.
*
* It extracts the token from the response and use it to do a simple API request
* fetching the latest sites from the user from Netlify.
*/
function handleAccessToken() {
// The access token is returned in the hash part of the document.location
// #access_token=1234&response_type=token&state=...
const response = new URLSearchParams(hash.replace(/^#/, ''));
const state = response.get('state');
const accessToken = response.get('access_token')
const savedState = localStorage.getItem('oauth_state');
// Clean up immediately
localStorage.removeItem('oauth_state');
if (!savedState || savedState !== state) {
console.log('Security Error: Invalid state parameter. Possible CSRF attack.');
return;
}
// Remove the token so it's not visible in the URL after we're done
document.location.hash = '';
// Send the access token to the create-token endpoint to generate a PAT
fetch('/create-token', {
method: "POST",
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(json => {
console.log(json);
})
.catch(error => {
console.log(error)
});
}
/*
* If we have any hash, it's because the user is coming back from Netlify and we
* can start doing API requests on their behalf.
*/
if (document.location.hash) {
handleAccessToken();
}

While the comments above do a decent job of breaking down the pieces of our js file — let’s take a moment to understand what’s happening:

  1. Set the variables for the needed CLIENT_ID and our REDIRECT_URI (we’re using localhost:8888 since we’re using netlify dev as our development server)
  2. Generate a random UUID to help track the state of the request returned back after completing the OAuth flow
  3. Build our OAuth URL from a few of the variables we’ve created so far to attach to our button back in the HTML code
  4. Once the OAuth flow is finished and we return a successful hash, we extract the values we need from the hash. We make sure the state matches, clear the hash from the URL, and begin the fetch to our /create-token endpoint
  5. Finally, we make sure we only handle the token if there is a hash located in the URL

All that’s left is to wire up our serverless function and generate our personal access token.

#Generating a personal access token

Let’s start by installing our single dependency in our project to help setup our function

Terminal window
npm install @netlify/functions

After successful OAuth authentication, the returned access token can be used as a Bearer token for Netlify API calls. However, for more granular control over authentication, Personal Access Tokens (PATs) provide additional benefits. PATs offer configurable lifespans and enhanced security features that may be better suited for production environments.

We will build an example of protecting our function using the OAuth access token, while also passing through that token to an API call generating our PAT.

Let’s create a file for our function using Netlify’s functions folder named create-token.ts and fill in our function:

netlify/functions/create-token.ts
import type { Context, Config } from "@netlify/functions"
export default async (req: Request, context: Context) => {
const accessToken = req.headers.get('Authorization')?.split(' ')[1]
if (!accessToken) {
return new Response('Unauthorized', { status: 401 })
}
try {
// Our fetch to our endpoint that creates the PAT includes a body describing the structure we explained earlier
const response = await fetch('https://api.netlify.com/api/v1/oauth/applications/create_token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
administrator_id: null,
expires_in: 604800,
grant_saml: false,
name: "token-from-state-example"
})
})
const data = await response.json()
return new Response(JSON.stringify({message: "Token created successfully!"}), {
headers: { 'Content-Type': 'application/json' }
})
} catch (error) {
return new Response(JSON.stringify({ error: 'Failed to create token' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
})
}
}
// This path config makes calling the endpoint easier, without having to match the route of the document
export const config: Config = {
path: "/create-token"
}

Above you’ll see we take and check for the access token we’ve sent through our original fetch. Applying that same token to the POST request that creates our new PAT and return a success message. This token is now usable, within it’s lifespan, to authorize new API requests made to Netlify.

#Final Thoughts

We’ve got everything we need to continue building out new API requests to Netlify. If you’d like to see the code we’ve built in repo, feel free to visit our examples repo and good luck with your projects!