npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

webba-id-sdk

v2.3.4

Published

SDK officiel pour intégrer l'authentification Webba ID dans vos applications

Readme

Webba ID SDK v2.3

Official SDK for OAuth 2.0 + PKCE authentication with Webba ID.

Version TypeScript


Installation

Copy src/lib/webba-id.ts into your project, or install via npm:

npm install webba-id-sdk

Required Configuration

Get your credentials at https://account.webba-creative.com/platform/applications

# .env
VITE_WEBBA_API_KEY=your_webba_id_supabase_anon_key
VITE_WEBBA_CLIENT_ID=wba_xxx
VITE_WEBBA_CLIENT_SECRET=wbs_xxx
VITE_WEBBA_REDIRECT_URI=http://localhost:5173/auth/callback

Usage

1. Configuration (main.tsx)

import { WebbaID } from '@/lib/webba-id'

WebbaID.configure({
  apiKey: import.meta.env.VITE_WEBBA_API_KEY,
  clientId: import.meta.env.VITE_WEBBA_CLIENT_ID,
  clientSecret: import.meta.env.VITE_WEBBA_CLIENT_SECRET,
  redirectUri: import.meta.env.VITE_WEBBA_REDIRECT_URI
})

2. Login (redirects to Webba ID)

// In a login button
async function handleLogin() {
  await WebbaID.login()
}

3. Callback (/auth/callback)

import { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { WebbaID } from '@/lib/webba-id'

export default function AuthCallback() {
  const navigate = useNavigate()
  const [error, setError] = useState<string | null>(null)
  const processingRef = useRef(false)

  useEffect(() => {
    const processCallback = async () => {
      // Prevent double execution (React StrictMode)
      if (processingRef.current) return
      processingRef.current = true

      if (!WebbaID.isCallback()) {
        setError('Invalid callback page')
        return
      }

      try {
        const tokens = await WebbaID.handleCallback()
        const user = await WebbaID.getUser(tokens.accessToken)

        // Save
        WebbaID.saveTokens(tokens)
        WebbaID.saveUser(user)

        navigate('/dashboard')
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Unknown error')
        processingRef.current = false
      }
    }

    processCallback()
  }, [navigate])

  if (error) return <div>Error: {error}</div>
  return <div>Connecting...</div>
}

4. Using Tokens

// Get stored user
const user = WebbaID.getStoredUser()
const tokens = WebbaID.getStoredTokens()

// Check if connected
if (user && tokens && !WebbaID.isTokenExpired(tokens)) {
  // User is connected
}

// Refresh expired tokens
if (tokens && WebbaID.isTokenExpired(tokens)) {
  try {
    const newTokens = await WebbaID.refreshTokens(tokens.refreshToken)
    WebbaID.saveTokens(newTokens)
  } catch {
    // Refresh failed, redirect to login
    WebbaID.clearTokens()
  }
}

5. Logout

async function handleLogout() {
  const tokens = WebbaID.getStoredTokens()
  await WebbaID.logout(tokens?.accessToken)
  window.location.href = '/'
}

API Reference

Configuration

interface WebbaIDConfig {
  apiKey: string      // Webba ID Supabase anon key (REQUIRED)
  clientId: string    // App key wba_xxx (REQUIRED)
  clientSecret?: string // App secret wbs_xxx (required for token exchange)
  redirectUri?: string  // Callback URL (default: origin + '/auth/callback')
  baseUrl?: string      // Webba ID URL (default: https://account.webba-creative.com)
  apiUrl?: string       // API URL (default: Supabase Edge Functions)
}

Methods

| Method | Description | |--------|-------------| | configure(config) | Configure the SDK (call once at startup) | | isConfigured() | Check if SDK is configured | | login() | Redirect to Webba ID for authentication | | handleCallback() | Handle OAuth callback and return tokens | | getUser(accessToken) | Get user info | | refreshTokens(refreshToken) | Refresh expired tokens | | logout(accessToken?) | Revoke token and clear storage | | isCallback() | Check if on callback page | | saveTokens(tokens) | Save tokens to localStorage | | getStoredTokens() | Get stored tokens | | clearTokens() | Remove stored tokens | | isTokenExpired(tokens) | Check if tokens are expired | | saveUser(user) | Save user to localStorage | | getStoredUser() | Get stored user |

Organizations

| Method | Description | |--------|-------------| | getOrganizations(accessToken) | List user's organizations | | getOrganization(accessToken, orgId) | Get organization details | | createOrganization(accessToken, data) | Create organization (becomes owner) | | updateOrganization(accessToken, orgId, data) | Update organization | | deleteOrganization(accessToken, orgId) | Delete organization | | getOrganizationMembers(accessToken, orgId) | List members | | inviteMember(accessToken, orgId, data) | Invite member by email | | removeMember(accessToken, orgId, memberId) | Remove member | | updateMemberRole(accessToken, orgId, memberId, role) | Change member role |

App Access

| Method | Description | |--------|-------------| | checkAppAccess(accessToken, orgId?) | Check if user has access (returns details) | | hasAccess(accessToken, orgId?) | Quick boolean check for access | | enableAppForOrg(accessToken, orgId) | Enable this app for an organization | | disableAppForOrg(accessToken, orgId) | Disable this app for an organization |

Linked Identities (OAuth Providers)

| Method | Description | |--------|-------------| | getLinkedIdentities(accessToken) | Get all linked OAuth providers | | hasProviderLinked(accessToken, provider) | Check if a specific provider is linked | | getProviderIdentity(accessToken, provider) | Get identity details for a provider | | getProviderId(accessToken, provider) | Get just the provider user ID | | hasCfxreLinked(accessToken) | Check if Cfx.re is linked (shortcut) | | getCfxreIdentity(accessToken) | Get Cfx.re identity details (shortcut) | | getCfxreId(accessToken) | Get just the Cfx.re user ID (shortcut) |

Reverse Lookup (Server-side only)

| Method | Description | |--------|-------------| | lookupUserByProvider(provider, providerUserId) | Find Webba ID user from provider ID | | lookupUsersByProvider(provider, providerUserIds[]) | Batch lookup (max 100) | | getUserIdentitiesByUserId(userId) | Get all linked identities for a user | | getProviderIdByUserId(userId, provider) | Get specific provider ID for a user | | getCfxreIdByUserId(userId) | Shortcut: Get Cfx.re ID for a user | | getDiscordIdByUserId(userId) | Shortcut: Get Discord ID for a user |

Note: These methods require appSecret and are for server-side use only (bots, APIs).

Types

interface AuthTokens {
  accessToken: string
  refreshToken: string
  expiresAt: number
  userId: string
}

interface WebbaUser {
  id: string
  email: string
  name: string
  firstName?: string
  lastName?: string
  avatar?: string
}

type OrgRole = 'owner' | 'admin' | 'sso_admin' | 'viewer' | 'member'

interface Organization {
  id: string
  name: string
  slug: string
  logo_url: string | null
  is_active: boolean
  created_at: string
  role: OrgRole
  members_count?: number
  domains_count?: number
}

interface OrganizationMember {
  id: string
  organization_id: string
  user_id: string | null
  role: OrgRole
  email?: string
  profile?: {
    first_name?: string
    last_name?: string
    display_name?: string
    avatar_url?: string
  }
}

interface CreateOrganizationData {
  name: string
  slug?: string
  logo_url?: string
}

interface InviteMemberData {
  email: string
  role?: 'admin' | 'sso_admin' | 'viewer' | 'member'
}

interface LinkedIdentity {
  provider: string
  provider_user_id: string
  provider_username: string | null
  provider_email: string | null
  provider_avatar_url: string | null
  linked_at: string
  last_used_at: string
}

interface CfxreIdentity {
  cfxre_id: string
  username: string | null
  avatar_url: string | null
  linked_at: string
}

interface AppAccessResult {
  has_access: boolean
  reason: 'subscription' | 'org_access' | 'subscription_and_org' | 'no_access'
  user_id: string
  app_id: string
  subscription: {
    id: string
    plan: string
    status: string
    features: Record<string, any> | null
    starts_at: string | null
    ends_at: string | null
  } | null
  organizations: Array<{
    id: string
    name: string
    slug: string
    role: string
  }>
}

interface LookupResult {
  provider_user_id: string
  user: {
    user_id: string
    email: string
  } | null
}

interface BatchLookupResult {
  mapping: Record<string, string>  // provider_user_id -> webba_user_id
}

interface UserIdentitiesResult {
  user_id: string
  identities: LinkedIdentity[]
}

OAuth Flow

1. User clicks "Login"
   |
2. WebbaID.login() → Redirect to account.webba-creative.com
   |
3. User authenticates on Webba ID
   |
4. Redirect back to /auth/callback?code=xxx&state=xxx
   |
5. WebbaID.handleCallback() → Exchange code for tokens
   |
6. WebbaID.getUser() → Get user info
   |
7. Save tokens & user → Redirect to dashboard

Security

PKCE (Proof Key for Code Exchange)

The SDK automatically uses PKCE to protect against interception attacks:

  • code_verifier randomly generated (32 bytes)
  • code_challenge = SHA-256(code_verifier) in base64url
  • Stored in cookie + localStorage (cross-origin safe)

State Parameter

The SDK generates a random state for each login and verifies it on callback (CSRF protection).

Token Storage

  • Tokens stored in localStorage (webba_tokens, webba_user)
  • PKCE data stored in cookie + localStorage with 10 minute expiration
  • Automatic cleanup of old PKCE sessions

Troubleshooting

"Session expired or invalid"

PKCE data has expired or is not accessible. Possible causes:

  • More than 10 minutes between login() and callback
  • Cookies blocked by browser
  • Different origin (e.g., 127.0.0.1 vs localhost)

Solution: Use the same origin (localhost) in development.

"Invalid or expired code"

The authorization code has already been used or expired.

Solution: Add useRef to prevent double execution in React StrictMode (see callback example above).

"clientSecret is required"

The clientSecret was not provided in configuration.

Solution: Add clientSecret: import.meta.env.VITE_WEBBA_CLIENT_SECRET to configure().


Organizations Example

import { WebbaID } from '@/lib/webba-id'

const tokens = WebbaID.getStoredTokens()

// List organizations
const orgs = await WebbaID.getOrganizations(tokens.accessToken)
console.log(orgs) // [{ id: '...', name: 'My Company', role: 'owner', ... }]

// Create organization
const newOrg = await WebbaID.createOrganization(tokens.accessToken, {
  name: 'My Company',
  slug: 'my-company' // optional
})

// Get organization details
const org = await WebbaID.getOrganization(tokens.accessToken, newOrg.id)
console.log(org.domains, org.members_count)

// Update organization (owner/admin)
await WebbaID.updateOrganization(tokens.accessToken, org.id, {
  name: 'New Name'
})

// List members
const members = await WebbaID.getOrganizationMembers(tokens.accessToken, org.id)

// Invite member (owner/admin)
await WebbaID.inviteMember(tokens.accessToken, org.id, {
  email: '[email protected]',
  role: 'member'
})

// Change member role (owner only)
await WebbaID.updateMemberRole(tokens.accessToken, org.id, memberId, 'admin')

// Remove member (owner/admin)
await WebbaID.removeMember(tokens.accessToken, org.id, memberId)

// Delete organization (owner only)
await WebbaID.deleteOrganization(tokens.accessToken, org.id)

App Access Example

Use this to control access to your application based on subscriptions or organization membership.

import { WebbaID } from '@/lib/webba-id'

const tokens = WebbaID.getStoredTokens()

// Full access check with details
const access = await WebbaID.checkAppAccess(tokens.accessToken)

if (!access.has_access) {
  // User doesn't have access
  console.log('Access denied. Reason:', access.reason)
  // Redirect to upgrade page or show error
  window.location.href = '/upgrade'
}

// Access granted - check details
if (access.subscription) {
  console.log('User plan:', access.subscription.plan)
  // access.subscription.features contains plan features
}

if (access.organizations.length > 0) {
  console.log('Organizations with access:', access.organizations)
  // User has org-level access through these organizations
}

// Quick boolean check (simpler)
if (await WebbaID.hasAccess(tokens.accessToken)) {
  // User can use the app
} else {
  // No access
}

// Check access for a specific organization
const orgAccess = await WebbaID.checkAppAccess(tokens.accessToken, 'org-uuid')

Access is granted if:

  • User has an active subscription for this app, OR
  • User is member of an organization where the app is enabled

Auto-enable for Organization

Apps can enable themselves for an organization when a user logs in:

import { WebbaID } from '@/lib/webba-id'

const tokens = WebbaID.getStoredTokens()

// Get user's organizations
const orgs = await WebbaID.getOrganizations(tokens.accessToken)

// Enable this app for the first organization
if (orgs.length > 0) {
  await WebbaID.enableAppForOrg(tokens.accessToken, orgs[0].id)
}

// Now checkAppAccess will return has_access: true for this org
const access = await WebbaID.checkAppAccess(tokens.accessToken, orgs[0].id)
console.log(access.has_access) // true

Provider Identity Examples

Check if a user has linked specific OAuth providers and retrieve their IDs:

import { WebbaID } from '@/lib/webba-id'

const tokens = WebbaID.getStoredTokens()

// ===================
// Generic methods (any provider)
// ===================

// Check if Discord is linked
if (await WebbaID.hasProviderLinked(tokens.accessToken, 'discord')) {
  const discordId = await WebbaID.getProviderId(tokens.accessToken, 'discord')
  console.log('Discord ID:', discordId)

  // Or get full identity
  const discord = await WebbaID.getProviderIdentity(tokens.accessToken, 'discord')
  console.log('Username:', discord.provider_username)
}

// Works with any provider: 'discord', 'google', 'github', 'azure', 'cfxre'
const hasGoogle = await WebbaID.hasProviderLinked(tokens.accessToken, 'google')

// ===================
// Cfx.re shortcuts
// ===================

if (await WebbaID.hasCfxreLinked(tokens.accessToken)) {
  const cfxreId = await WebbaID.getCfxreId(tokens.accessToken)
  console.log('Cfx.re ID:', cfxreId) // "123456"
}

// ===================
// Get all linked providers
// ===================

const identities = await WebbaID.getLinkedIdentities(tokens.accessToken)
// [
//   { provider: 'cfxre', provider_user_id: '123456', ... },
//   { provider: 'discord', provider_user_id: '789...', ... }
// ]

Reverse Lookup (Server-side)

Find Webba ID users from their provider IDs (e.g., Discord ID → Webba ID user). Requires appSecret - server-side only.

import { WebbaID } from 'webba-id-sdk'

// Server-side configuration (with appSecret)
const webbaId = new WebbaID({
  appKey: 'wba_xxx',
  appSecret: 'wbs_xxx',  // Required for reverse lookup
  redirectUri: 'https://example.com/callback'
})

// ===================
// Single lookup
// ===================

// Example: Discord bot finding Webba ID user from Discord user ID
const result = await webbaId.lookupUserByProvider('discord', message.author.id)

if (result.user) {
  console.log('Webba ID:', result.user.user_id)
  console.log('Email:', result.user.email)
} else {
  console.log('This Discord user is not linked to any Webba ID account')
}

// ===================
// Batch lookup (max 100)
// ===================

// Example: Check multiple Discord users at once
const discordIds = ['123456789', '987654321', '111222333']
const batch = await webbaId.lookupUsersByProvider('discord', discordIds)

console.log(batch.mapping)
// { '123456789': 'webba-uuid-1', '987654321': 'webba-uuid-2' }
// Note: '111222333' is absent = not linked

// Check if a specific ID is linked
if (batch.mapping['123456789']) {
  console.log('User is linked:', batch.mapping['123456789'])
}

// ===================
// Get identities by user_id
// ===================

// Get all linked identities for a Webba ID user
const result = await webbaId.getUserIdentitiesByUserId('webba-user-uuid')
console.log(result.identities)
// [
//   { provider: 'discord', provider_user_id: '123456789', provider_username: 'User#1234', ... },
//   { provider: 'cfxre', provider_user_id: '987654', provider_username: 'Player', ... }
// ]

// Get specific provider ID
const cfxreId = await webbaId.getProviderIdByUserId('webba-user-uuid', 'cfxre')
console.log('Cfx.re ID:', cfxreId) // '987654' or null

// Shortcuts for common providers
const cfxre = await webbaId.getCfxreIdByUserId('webba-user-uuid')
const discord = await webbaId.getDiscordIdByUserId('webba-user-uuid')

Supported providers: discord, google, github, azure, cfxre


AuthContext Example

import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
import { WebbaID, AuthTokens, WebbaUser } from '@/lib/webba-id'

interface AuthContextType {
  user: WebbaUser | null
  tokens: AuthTokens | null
  isLoading: boolean
  login: () => Promise<void>
  logout: () => Promise<void>
  setAuth: (tokens: AuthTokens, user: WebbaUser) => void
}

const AuthContext = createContext<AuthContextType | null>(null)

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<WebbaUser | null>(null)
  const [tokens, setTokens] = useState<AuthTokens | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    const storedTokens = WebbaID.getStoredTokens()
    const storedUser = WebbaID.getStoredUser()

    if (storedTokens && storedUser && !WebbaID.isTokenExpired(storedTokens)) {
      setTokens(storedTokens)
      setUser(storedUser)
    }
    setIsLoading(false)
  }, [])

  const login = async () => {
    await WebbaID.login()
  }

  const logout = async () => {
    await WebbaID.logout(tokens?.accessToken)
    setUser(null)
    setTokens(null)
  }

  const setAuth = (newTokens: AuthTokens, newUser: WebbaUser) => {
    WebbaID.saveTokens(newTokens)
    WebbaID.saveUser(newUser)
    setTokens(newTokens)
    setUser(newUser)
  }

  return (
    <AuthContext.Provider value={{ user, tokens, isLoading, login, logout, setAuth }}>
      {children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) throw new Error('useAuth must be used within AuthProvider')
  return context
}

License

MIT - Webba Creative Technologies 2026