DiscordStrategy
The Discord strategy is used to authenticate users against a Discord account. It extends the OAuth2Strategy.
Usage
Create an OAuth application
First go to the Discord Developer Portal to create a new application and get a client ID and secret. The client ID and secret are located in the OAuth2 Tab of your Application. Once you are there you can already add your first redirect url, f.e. http://localhost:3000/auth/discord/callback
.
You can find the detailed Discord OAuth Documentation here.
Create your session storage
import { createCookieSessionStorage } from "@remix-run/node";
export const sessionStorage = createCookieSessionStorage({
cookie: {
name: "_session",
sameSite: "lax",
path: "/",
httpOnly: true,
secrets: ["s3cr3t"],
secure: process.env.NODE_ENV === "production",
},
});
export const { getSession, commitSession, destroySession } = sessionStorage;
Create the strategy instance
import { Authenticator } from "remix-auth";
import type { DiscordProfile, PartialDiscordGuild } from "remix-auth-discord";
import { DiscordStrategy } from "remix-auth-discord";
import { sessionStorage } from "~/session.server";
type CustomDiscordGuild = Omit<PartialDiscordGuild, "features">;
export interface DiscordUser {
id: DiscordProfile["id"];
displayName: DiscordProfile["displayName"];
avatar: DiscordProfile["__json"]["avatar"];
email: DiscordProfile["__json"]["email"];
locale?: string;
guilds?: Array<CustomDiscordGuild>;
accessToken: string;
refreshToken: string;
}
export const auth = new Authenticator<DiscordUser>(sessionStorage);
const discordStrategy = new DiscordStrategy(
{
clientID: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
callbackURL: "https://example.com/auth/discord/callback",
scope: ["identify", "email", "guilds"],
},
async ({
accessToken,
refreshToken,
extraParams,
profile,
}): Promise<DiscordUser> => {
const userGuilds: Array<PartialDiscordGuild> = await (
await fetch("https://discord.com/api/v10/users/@me/guilds", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
)?.json();
const guilds: Array<CustomDiscordGuild> = userGuilds
.filter(
(g) =>
g.owner || (BigInt(g.permissions) & BigInt(0x20)) == BigInt(0x20),
)
.map(({ features, ...rest }) => {
return { ...rest };
});
return {
id: profile.id,
displayName: profile.displayName,
avatar: profile.__json.avatar,
email: profile.__json.email,
locale: profile.__json.locale,
accessToken,
refreshToken,
guilds,
};
},
);
auth.use(discordStrategy);
Setup your routes
import { Form } from "@remix-run/react";
export default function Login() {
return (
<Form action="/auth/discord" method="post">
<button>Login with Discord</button>
</Form>
);
}
import type { ActionFunction, LoaderFunction } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { auth } from "~/auth.server";
export let loader: LoaderFunction = () => redirect("/login");
export let action: ActionFunction = ({ request }) => {
return auth.authenticate("discord", request);
};
import type { LoaderFunction } from "@remix-run/node";
import { auth } from "~/auth.server";
export let loader: LoaderFunction = ({ request }) => {
return auth.authenticate("discord", request, {
successRedirect: "/dashboard",
failureRedirect: "/login",
});
};
import type { LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { auth } from "~/auth.server";
import type { DiscordUser } from "~/auth.server";
export let loader: LoaderFunction = async ({ request }) => {
return await auth.isAuthenticated(request, {
failureRedirect: "/login",
});
};
export default function DashboardPage() {
const user = useLoaderData<DiscordUser>();
return (
<div>
<h1>Dashboard</h1>
<h2>Welcome {user.displayName}</h2>
</div>
);
}
That's it, try going to /login
and press the Login button to start the authentication flow. Make sure to store all your Secrets properly and setup the correct redirect_url once you go to production.