Skip to main content
Version: Next
note

Last checked with Wasp 0.23.

This guide depends on external libraries or services, so it may become outdated over time. We do our best to keep it up to date, but make sure to check their documentation for any changes.

Custom OAuth Provider

This guide shows you how to implement a custom OAuth provider in your Wasp application. We'll use Spotify as an example, but the same approach works for any OAuth provider.

Prerequisites

Setting up a Custom OAuth Provider

1. Configure main.wasp

Set up the auth configuration and API routes:

main.wasp
app SpotifyOauth {
wasp: {
version: "^0.21.0"
},
title: "spotify-oauth",
auth: {
userEntity: User,
onAuthFailedRedirectTo: "/",
methods: {
// Enable at least one OAuth provider so Wasp exposes OAuth helpers
google: {}
}
},
}

route RootRoute { path: "/", to: MainPage }
page MainPage {
component: import { MainPage } from "@src/MainPage",
}

api authWithSpotify {
httpRoute: (GET, "/auth/spotify"),
fn: import { authWithSpotify } from "@src/auth",
entities: []
}

api authWithSpotifyCallback {
httpRoute: (GET, "/auth/spotify/callback"),
fn: import { authWithSpotifyCallback } from "@src/auth",
entities: []
}
note

The route names are arbitrary, but the path on authWithSpotifyCallback must match the redirect URI you register with your provider. Spotify rejects localhost over HTTP, so register http://127.0.0.1:3001/auth/spotify/callback in the Spotify Developer Dashboard and set Wasp's URLs to match in step 2.

2. Configure environment variables

Create or update your .env.server file:

.env.server
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret

# You may need dummy values for built-in providers if you're using them
# just to get the arctic package installed
GOOGLE_CLIENT_ID=x
GOOGLE_CLIENT_SECRET=x

# Spotify rejects `localhost`, so point Wasp at 127.0.0.1 instead
WASP_SERVER_URL=http://127.0.0.1:3001
WASP_WEB_CLIENT_URL=http://127.0.0.1:3000
.env.client
REACT_APP_API_URL=http://127.0.0.1:3001
note

Most OAuth providers accept localhost directly; the 127.0.0.1 setup above is specific to Spotify. Other providers (e.g., Slack) require a real hostname. See Local Network Testing for the nip.io workaround.

3. Set up the database schema

Update your Prisma schema to store the user data you need:

schema.prisma
model User {
id String @id @default(cuid())
name String
profilePicture String
}

4. Implement the OAuth handlers

The implementation splits across two files: pure Spotify logic in src/spotify.ts, and Wasp auth logic in src/auth.ts.

src/spotify.ts holds the Arctic client (the library Wasp uses for OAuth) and the Spotify /me profile fetch:

src/spotify.js
import * as arctic from "arctic";
import { config } from "wasp/server";
import * as z from "zod";

if (!process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET) {
throw new Error(
"Please provide SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in .env.server file",
);
}

const clientId = process.env.SPOTIFY_CLIENT_ID;
const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;
const redirectURI = `${config.serverUrl}/auth/spotify/callback`;

export const spotify = new arctic.Spotify(clientId, clientSecret, redirectURI);

// Spotify user schema for validation
const spotifyUserSchema = z.object({
id: z.string(),
display_name: z.string(),
external_urls: z.object({
spotify: z.string(),
}),
images: z.array(
z.object({
url: z.string(),
height: z.number(),
width: z.number(),
}),
),
});

export async function getSpotifyUser(accessToken) {
const response = await fetch("https://api.spotify.com/v1/me", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return spotifyUserSchema.parse(await response.json());
}

src/auth.ts wires the route handlers and uses wasp/server/auth helpers (findAuthIdentity, createUser) to connect the OAuth identity to a Wasp session (see Custom Auth Actions for details). This is similar to what Wasp does internally for Google, GitHub and other supported providers:

src/auth.js
import * as arctic from "arctic";
import {
createUser,
findAuthIdentity,
getRedirectUriForOneTimeCode,
tokenStore,
} from "wasp/server/auth";
import { spotify, getSpotifyUser } from "./spotify";

// Handler for /auth/spotify - initiates OAuth flow
export const authWithSpotify = async (req, res) => {
const state = arctic.generateState();
const url = await spotify.createAuthorizationURL(state, {
scopes: ["user-read-email"],
});
res.redirect(url.toString());
};

// Handler for /auth/spotify/callback - processes OAuth callback
export const authWithSpotifyCallback = async (req, res) => {
const code = req.query.code;
const tokens = await spotify.validateAuthorizationCode(code);
const spotifyUser = await getSpotifyUser(tokens.accessToken);

const providerId = {
providerName: "spotify",
providerUserId: spotifyUser.id,
};

const existingIdentity = await findAuthIdentity(providerId);
const authId = existingIdentity
? existingIdentity.authId
: await createUserFromSpotifyProfile(providerId, spotifyUser);

const oneTimeCode = await tokenStore.createToken(authId);
return res.redirect(getRedirectUriForOneTimeCode(oneTimeCode).toString());
};

async function createUserFromSpotifyProfile(providerId, spotifyUser) {
const userData = {
name: spotifyUser.display_name,
profilePicture:
spotifyUser.images[1]?.url ?? spotifyUser.images[0]?.url ?? "",
};
const user = await createUser(
providerId,
JSON.stringify(spotifyUser),
userData,
);
return user.auth.id;
}
note

The tokenStore and getRedirectUriForOneTimeCode are internal Wasp APIs that may change in future versions. This guide relies on them because there is currently no public API for implementing fully custom OAuth flows.

5. Create the login page

Add a login button that redirects to your OAuth endpoint. This follows the same pattern as Wasp's custom social auth UI:

src/MainPage.jsx
import { logout, useAuth } from "wasp/client/auth";
import { config } from "wasp/client";

export const MainPage = () => {
const { data: user } = useAuth();

return (
<div className="container">
<main>
{user ? (
<div>
<img src={user.profilePicture} alt="profile" />
<br />
Logged in as {user.name}
<br />
<button onClick={logout}>Log out</button>
</div>
) : (
<p>Not logged in</p>
)}
<div className="buttons">
<a
className="button button-filled"
href={`${config.apiUrl}/auth/spotify`}
>
Login with Spotify
</a>
</div>
</main>
</div>
);
};

Using a Different OAuth Provider

Arctic (v1) supports many providers. Check its documentation for the full list and their specific setup requirements.

Each provider follows the same pattern. For example, to use Twitch instead of Spotify:

  • Swap the Arctic provider: new arctic.Twitch(clientId, clientSecret, redirectURI).
  • Fetch the user from https://api.twitch.tv/helix/users with the appropriate scopes (e.g., user:read:email).
  • Update the User schema and the fields passed to createUser to match the provider's response.