Internal tools and small, well-scoped projects are a great avenue to tinker with technologies on the periphery of your understanding, and a Third South project has led me to spin up a small Next project using Bun [1] and Auth.js (nee next-auth), which has been quite bad and I think successfully dissuaded me from using it in any more serious endeavor.

Auth.js’s plugin-based provider system is a double-edged sword: when it works, the code foodprint is lovely and small, but when it doesn’t work you get obscure, typo-riddled errors and no easy way to introspect what’s actually happening. This issue is exacerbated by the fact that the most recent change (and concomitant rebrand) significantly changed the provider API, and providers within the core repository are, at the time of this writing, still broken by those changes.

But! I am not here to rag on Auth.js! I am here to share a working Square provider implementation. This is an evolution of this example I found, which is directionally helpful but on an older version of the API. Here you go:

  // Replace `clientId` and `clientSecret` with your own.
  clientId: "clientId",
  clientSecret: "clientSecret",

// This is for sandbox environment. For production, use ``.
const BASE_URL = "";

const CONFIG = {
  id: "square",
  name: "Square",

  // This disables the CSRF check for the provider, is completely undocumented
  // by the 'making your own provider' guide, and is responsible for me losing
  // 3 hours of my life. I hope you enjoy it.
  checks: ["none"],

  // Pass in the `client_secret` in the request body.
  client: { token_endpoint_auth_method: "client_secret_post" },
  type: "oauth",
  authorization: {
    url: `${BASE_URL}/oauth2/authorize`,
    params: {
      // Replace the `scope` with the permissions you need.
      clientId: CREDENTIALS.clientId,
      session: false,
  url: `${BASE_URL}/oauth2`,
  token: `${BASE_URL}/oauth2/token`,
  userinfo: `${BASE_URL}/v2/merchants`,

  // You could do with better typing here, but that's left as an exercise to the reader.
  profile: (profile: any) => {
    const unwrappedProfile = profile.merchant[0];
    return {
      name: unwrappedProfile.business_name,
      email: unwrappedProfile.owner_email,

export default CONFIG;

  1. Which has, thus far, been so easy and banal to use it warrants no further discussion ↩︎

Lightning bolt
Subscribe to my newsletter

I publish monthly roundups of everything I've written, plus pictures of my corgi.
© 2024 Justin Duke · All rights reserved · have a nice day.