Documentation

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.

The VitNode login page with a Continue with heading over a blurple Discord button, a neutral Google button with the four-color G and a blue Facebook button, next to the email form

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:

src/utils/sso/github.ts
  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 bytes
What the login page will accept

The 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, a DOCTYPE and comments around it are fine and get stripped.
  • Nothing executable: no <script>, no onload-style attributes, no javascript: 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 a data: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:

apps/web/src/utils/sso/github.ts
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:

apps/web/src/vitnode.api.config.ts
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:

src/utils/sso/github.ts
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:

src/utils/sso/github.ts
  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.

apps/web/src/router.tsx
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