@aethex.os/passport
v1.0.0
Published
OAuth federation for multi-provider auth — link GitHub, Discord, Google, and more to a single unified user identity
Maintainers
Readme
@aethex.os/passport
OAuth federation for multi-provider auth — let users log in with GitHub, Discord, Google, Roblox, or any provider, and link them all to a single unified identity. Storage-adapter pattern means it works with any database.
Install
npm install @aethex.os/passportConcept
Instead of one user per OAuth provider, @aethex.os/passport maintains a Foundation Passport — one canonical user record that can have multiple OAuth providers attached to it. Log in with Discord, later link GitHub, they're the same user.
Setup with Supabase
import { createClient } from "@supabase/supabase-js";
import { createPassportManager, createSupabasePassportAdapter } from "@aethex.os/passport";
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE!);
const passport = createPassportManager({
storage: createSupabasePassportAdapter(supabase),
});Required Supabase tables:
create table user_profiles (
id uuid primary key default gen_random_uuid(),
email text unique not null,
username text unique not null,
full_name text,
avatar_url text,
created_at timestamptz default now()
);
create table provider_identities (
user_id uuid references user_profiles(id),
provider text not null,
provider_user_id text not null,
email text,
username text,
avatar_url text,
linked_at timestamptz default now(),
primary key (user_id, provider)
);Login / sign-up (federation)
// In your Discord/GitHub/Google OAuth callback
const result = await passport.federateOAuthUser("discord", {
id: discordUser.id,
email: discordUser.email,
username: discordUser.username,
name: discordUser.global_name,
avatar: `https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.webp`,
});
console.log(result.userId); // Foundation Passport ID
console.log(result.isNewUser); // true if just signed upLink an additional provider
// User is already logged in, now linking GitHub to their account
await passport.linkProvider(currentUserId, "github", {
id: githubUser.id,
email: githubUser.email,
username: githubUser.login,
avatar: githubUser.avatar_url,
});Unlink a provider
// Prevents unlinking the last auth method
await passport.unlinkProvider(userId, "github");List linked providers
const providers = await passport.getLinkedProviders(userId);
// ["discord", "github"]Custom storage adapter
Works with any database by implementing the PassportStorageAdapter interface:
import type { PassportStorageAdapter } from "@aethex.os/passport";
const myAdapter: PassportStorageAdapter = {
findIdentity: async (provider, providerUserId) => { /* ... */ },
createUser: async (data) => { /* return new userId */ },
linkProvider: async (userId, provider, providerUserId, data) => { /* ... */ },
findUserByEmail: async (email) => { /* return userId or null */ },
listUserProviders: async (userId) => { /* return provider[] */ },
unlinkProvider: async (userId, provider) => { /* ... */ },
};
const passport = createPassportManager({ storage: myAdapter });Testing with in-memory adapter
import { createPassportManager, createMemoryPassportAdapter } from "@aethex.os/passport";
const passport = createPassportManager({ storage: createMemoryPassportAdapter() });API
| Method | Description |
|--------|-------------|
| passport.federateOAuthUser(provider, user) | Login or sign-up via any OAuth provider |
| passport.linkProvider(userId, provider, user) | Link an additional provider to an existing user |
| passport.unlinkProvider(userId, provider) | Unlink a provider (guards against unlinking the last one) |
| passport.getLinkedProviders(userId) | List all providers linked to a user |
Part of the @aethex.os toolkit.
