Skip to content

How to use Freeagent's OAuth API with Next Auth (Auth.js)

Jon Sherrard
2 min read

Table of Contents

Note: An earlier version of this post described an OAuth bug as being part of the Next Auth library - it was actually due to an implementation detail of the Freeagent API.

Hi there intrepid googler. Like me, you want to interface with Freeagent.com's API. Programmatically administering your accounting software can be pretty fun. And Next.js and Auth.js (formally Next Auth) are a great way to do it.

Next Auth has a tonne of custom providers, which let you authenticate with your app (login, register etc), using a 3rd party. It does not have one for Freeagent. But you can write raw, or plain providers that will work with the OAuth 2.0 spec.

The Freeagent Provider configuration:

{
  type: "oauth",
  id: "freeagent",
  name: "FreeAgent",
  clientId: env.FREEAGENT_CLIENT_ID,
  clientSecret: env.FREEAGENT_CLIENT_SECRET,
  authorization: "https://api.freeagent.com/v2/approve_app?response_type=code",
  token: "https://api.freeagent.com/v2/token_endpoint",
  userinfo: {
    url: `https://api.freeagent.com/v2/users/me`,
  }
},

The basic code you SHOULD need to set up Freeagent Oauth (there's one more step)

This should work, but due to a bug in Freeagent's OAuth implementation which we need to work around.

Their token endpoint returns:

{
 "access_token":"2YotnFZFEjr1zCsicMWpAA",
 "token_type":"bearer", // Note lower case `bearer`
 "expires_in":3600,
 "refresh_token":"txtvz3JOkF0XG5Qx2TlKWIA",
 "refresh_token_expires_in":631151957
}

Next Auth V4 uses https://github.com/panva/node-openid-client under the hood which uses the `token_type` to make the subsequent request to the `/users/me` endpoint.

Sadly, Freeagent's API does not accept lower cases bearer token_type, so we need to write our own request callback that fixes this.

Thank you to Next Auth author Balázs Orbán for finding this out for me!
https://x.com/balazsorban44/status/1726967157966188828?s=20

async request(context) {
  const profile = await fetch(
    `https://api.freeagent.com/v2/users/me?`,
    {
      headers: {
        Authorization: `Bearer ${context.tokens.access_token}`,
      },
    },
  ).then(async (res) => await res.json());
  return profile;
},

The sum purpose of this code is to capitalize the B in Bearer, which is a bit annoying, but there it is.

userinfo: {
  url: `https://api.freeagent.com/v2/users/me`,
  async request(context) {
    const profile = await fetch(
    `https://api.freeagent.com/v2/users/me?`,
    {
      headers: {
        Authorization: `Bearer ${context.tokens.access_token}`,
      },
    },
  ).then(async (res) => await res.json());
  return profile;
  },
},

Updating the next-auth Database schema

The final step is to update the prisma schema used by next-auth to account for a field returned by Freeagent.

model Account {
  refresh_token_expires_in Int?
}

Run npx prisma migrate dev and you're ready to rock.

If you'd like my code for making authenticated requests to the Freeagent API, and refreshing the token every hour (that's how long you get from Freeagent), ping me on twitter @jshez, and I'll send the files.

Jon Sherrard Twitter

CTO at Ashore.io - Writing about web technology, cross-platform React Native development, & the UK Startup scene.