SSO
Provider icons
Put a brand mark and a brand color on your SSO login buttons, with an inline SVG or image URL and a hex color from the adapter, or a React component registered in the browser.
Every SSO provider gets a button labelled with its name on /login and
/register (where they go). The built-in adapters already carry their
brand marks and, where the brand allows, a fill color, so those buttons look
right without any configuration. Reddit, GitLab, Twitch, Slack and Microsoft
stay neutral outline buttons with their mark in color, so the label always
keeps strong contrast. A custom adapter starts out as a neutral button with
text only, like a GitHub button with no octocat. This page shows how to
give it a mark and a color.

From the adapter (SVG or image)
Add an icon to the object your adapter returns. It travels to the browser with
the rest of the provider list, so it has to be a string: inline SVG markup, or a
URL the browser can load as an image.
This excerpt adds it to the object the GitHub adapter from Custom adapter returns:
return {
id,
name: "GitHub",
icon: '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" /></svg>',
// ... profileFields, getUrl, fetchToken, fetchUser
}Use fill="currentColor" and leave the width and height off. The mark then
takes the button's text color in light and dark mode, including white on a
brand-colored button, and is sized to match the label. A
multicolor mark, such as Google's four-color G, keeps its own fills; give its
adapter no brand color so it sits on the neutral button it was designed for.
An image works the same way. Use any one of these:
icon: "https://cdn.example.com/brands/github.svg", // remote file
icon: "/icons/github.png", // something in your app's public folder
icon: "data:image/png;base64,iVBORw0KGgo...", // inlined bytesThe markup is inserted into the page, so it is checked before it renders:
- Inline markup must be a single
<svg>...</svg>element. An XML prolog, aDOCTYPEand comments around it are fine and get stripped. - Nothing executable: no
<script>, noonload-style attributes, nojavascript:URLs, no<foreignObject>, and no<style>block. A<style>inside inline SVG is not scoped, so it would restyle the whole page. - Image URLs must be
https:,http:, a root-relative path (/icons/x.png), or adata:image/...URI. - At most 16 KB.
An icon that fails any of those is dropped. The button keeps its name and still signs people in. You lose the mark, never the provider. In development the console says which provider's icon was dropped, so a silent button is never a mystery.
Importing an .svg file
You do not have to paste the markup. Give the adapter factory an icon option
and pass the file in from the config. In the factory from Custom
adapter:
export const GitHubSSOApiPlugin = ({
clientId,
clientSecret,
icon,
}: {
clientId: string
clientSecret: string
icon?: string
}): SSOApiPlugin => {
// ...
return {
id,
name: "GitHub",
icon,
// ... profileFields, getUrl, fetchToken, fetchUser
}
}Where the config is bundled by Vite (apps/web, or any generated single-app
VitNode project), import the file itself:
import { buildApiConfig } from "@vitnode/core/vitnode.config"
import githubIcon from "./icons/github.svg?raw" // the file's markup
import { GitHubSSOApiPlugin } from "./utils/sso/github"
export const vitNodeApiConfig = buildApiConfig({
authorization: {
ssoAdapters: [
GitHubSSOApiPlugin({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
icon: githubIcon,
}),
],
},
// ...
})?raw hands you the file's whole contents, including the prolog, DOCTYPE and
generator comment. A plain import githubLogo from "./icons/github.svg" hands you a URL instead
(/assets/github-a1b2c3.svg after a build, or an inlined
data:image/svg+xml,... for a small file), and that works as an icon too. Prefer
?raw when you want the mark to follow the button's text color, since a file
loaded through <img> cannot see currentColor.
A standalone API app such as apps/api runs on Node with no bundler, so an
import of an .svg is not a module there. Read the file instead:
import { readFileSync } from 'node:fs'
const githubIcon = readFileSync(
new URL('./github.svg', import.meta.url),
'utf8',
)Read it once at module scope, not inside getUrl. Also, tsc does not
copy .svg files into dist, so either copy it as part of your build or keep
the markup in a .ts constant, the way VitNode's own adapters do.
If you would rather have a component than a string, register it in the browser instead, as From the browser shows.
Brand color
Add a brandColor to fill the button with the provider's color, the way the
built-in Discord (#5865F2) and Facebook (#0866FF) adapters do:
return {
id,
name: "GitHub",
icon: githubIcon,
brandColor: "#24292F",
// ... profileFields, getUrl, fetchToken, fetchUser
}The value has to be a hex color, #RGB or #RRGGBB. The button picks its
own text color: white when white text reaches a 4.5:1 contrast ratio on your
color, near-black otherwise, so a yellow brand still gets a readable label. A
currentColor icon follows that text color.
Leave brandColor out and the button stays neutral, with a border. That is the
right choice for providers whose guidelines ask for a neutral button, such as
Google and Microsoft. A value that is not a hex color is ignored the same way,
and in development the console names the provider.
From the browser (React component or JSX)
To ship an icon as a component, such as a lucide-react icon, a component
from your own design system or plain JSX, register it by provider id. A registered icon wins over whatever the adapter sent.
import { configureSSOIcons } from '@vitnode/core/tanstack/auth'
import { GitLabIcon } from './components/icons/gitlab' // your own component
configureSSOIcons({
gitlab: GitLabIcon, // a component: rendered with `className="size-4"`
github: <img alt="" className="size-4" src="/icons/github.png" />, // or JSX
})Call it from a module your router entry imports, so it has run before the login
screen renders. configureSSOIcons merges, so you can call it more than once,
for example once per plugin.
This is also the escape hatch for a provider whose mark you cannot express as one static SVG: an animated logo, a theme-aware pair of files, or an icon that needs a wrapper element.
Reference
Prop
Type