Integrations
React SDK

React SDK Reference

camel-accounts-react is the official React SDK for "Sign in with Camel Accounts". It wraps the OAuth 2.0 + OpenID Connect flow behind a context provider, a styled login button, and a small hook — handling PKCE, CSRF state, and the code exchange for you.

  • Package: camel-accounts-react
  • Exports: CamelAuthProvider, CamelAuthContext, CamelLoginButton, useCamelAuth, and the shared types
  • Styles: camel-accounts-react/style.css
  • Format: ESM (camel-accounts-react.js) + UMD (camel-accounts-react.umd.cjs)
  • Peer deps: React ^18 || ^19 and react-dom
  • Browser support: requires Web Crypto (crypto.subtle) and postMessage
npm install camel-accounts-react

Exports

ExportKindDescription
CamelAuthProvidercomponentWraps your app and provides auth state/actions
CamelAuthContextcontextRaw React context (usually via useCamelAuth)
CamelLoginButtoncomponentGoogle-style "Sign in with Camel Accounts" button
useCamelAuthhookReads auth state + actions; errors if used outside the provider
CamelAuthConfigtypeProvider config
CamelAuthTokenstypeToken pair returned after login
CamelAuthUsertypeUserinfo claims
AuthStatetypeAuthentication snapshot

Quick start

import { CamelAuthProvider, CamelLoginButton, useCamelAuth } from "camel-accounts-react";
import "camel-accounts-react/style.css";
 
function App() {
  return (
    <CamelAuthProvider
      config={{ issuer: "https://accounts.camelcreatives.com", clientId: "your-app" }}
    >
      <LoginSection />
    </CamelAuthProvider>
  );
}
 
function LoginSection() {
  const { user, authenticated, loading, login, logout } = useCamelAuth();
 
  if (loading) return <span>Loading…</span>;
 
  return authenticated ? (
    <div>
      Signed in as {user?.name ?? user?.email}
      <button onClick={logout}>Sign out</button>
    </div>
  ) : (
    <CamelLoginButton onClick={login} />
  );
}

CamelAuthProvider

PropTypeDefaultDescription
config.issuerstring(required)Camel Accounts base URL, e.g. https://accounts.camelcreatives.com
config.clientIdstring(required)Your registered client_id
config.scopesstring[]["openid", "profile", "email"]OAuth scopes requested on login
config.redirectPathstring"/widget-callback"Path on the issuer used as the popup callback (must be in your client's registered redirect URIs)
config.storage"memory" | "localStorage""memory"Where tokens/PKCE state persist

The provider renders <CamelAuthContext.Provider> and must appear once near the top of your tree.

useCamelAuth()

Must be called within a <CamelAuthProvider>; throws otherwise.

PropertyTypeDescription
userCamelAuthUser | nullClaims fetched from /oauth/userinfo
tokensCamelAuthTokens | null{ accessToken, refreshToken, expiresIn, scope }
authenticatedbooleantrue while an access token exists
loadingbooleantrue during initial token restoration (localStorage mode)
login() => voidOpens the popup OAuth flow
logout() => voidClears local tokens + user; closes any popup
getAccessToken() => string | nullReturns the current access token for API calls

CamelLoginButton

PropTypeDefaultDescription
onClick() => voidHandler; pass login from useCamelAuth
disabledbooleanfalseDisables the button
labelstring"Sign in with Camel Accounts"Button text
logostring(built-in camel mark)URL of an image shown on the left (Google-style)
<CamelLoginButton onClick={login} logo="/camel-creatives.png" />

Styling

Import camel-accounts-react/style.css for the default Google-clean look. The button uses BEM class names so you can override freely:

  • .camel-login-btn — the button
  • .camel-login-btn__icon — the fallback camel SVG
  • .camel-login-btn__logo — the passed-in logo image (rendered round, object-fit: cover)

OAuth flow under the hood

  1. login() generates a PKCE verifier/challenge (S256) and a random CSRF state.
  2. PKCE verifier + state are stashed in localStorage (or sessionStorage as a fallback).
  3. A 500×600 centered popup opens /oauth/authorize?response_type=code&...&code_challenge=...&state=....
  4. The user signs in and consents inside the popup (Camel Accounts' own UI).
  5. The popup lands on /widget-callback and postMessages { type: "camel-auth-callback", code, state } to the opener.
  6. The SDK verifies state (CSRF protection), then exchanges the code for tokens at /oauth/token.
  7. Tokens + userinfo are surfaced through useCamelAuth() and the popup closes.

Because the callback route is hosted on the issuer itself, your app needs no callback route or listener wiring.

Security model

  • PKCE S256 on every flow — the code verifier never leaves the browser.
  • CSRF state is validated before a code is exchanged; a mismatch is logged and dropped.
  • No tokens are stored on the issuer; they live in your app only.
  • "memory" storage keeps tokens in React state only — they are lost on refresh and never written to disk.
  • "localStorage" storage persists tokens under the camel_auth_* keys. Only use this when the browser is fully trusted; for a production SPA prefer "memory" in front of your own httpOnly-cookie session (see Web SPA).

Storage modes

ModePersistenceRefreshBest for
"memory" (default)React state only; lost on refreshOffSPAs with their own cookie session
"localStorage"Survives refreshes under camel_auth_tokensOn (state restored on mount)Trusted-browser apps with no backend

Access tokens & refresh

  • Access tokens have a 15-minute TTL.
  • The SDK does not auto-refresh. On a 401, either exchange the refresh token yourself or have the user sign in again:
const res = await fetch("/api/orders", {
  headers: { Authorization: `Bearer ${getAccessToken()}` },
});
 
if (res.status === 401) {
  const body = new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: tokens.refreshToken,
    client_id: "your-app",
  });
  const refresh = await fetch("https://accounts.camelcreatives.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body,
  });
  // store the new access token / user, then retry the request
}

Receiving the callback postMessage

If you are not using CamelAuthProvider and want to handle the callback yourself, listen for:

window.addEventListener("message", (e) => {
  const msg = e.data;
  if (msg?.type !== "camel-auth-callback" || !msg.code) return;
  // verify msg.state against the stashed state, then exchange msg.code
});

Popup notes & troubleshooting

  • Popup blocked: browsers block popups not triggered by a direct user gesture — always call login() from an event handler (onClick), never in an effect or on a timer.
  • useCamelAuth outside provider: throws "useCamelAuth must be used within a <CamelAuthProvider>".
  • State mismatch: logged as "camel-accounts: state mismatch — possible CSRF"; the exchange is dropped.
  • Missing verifier: logged as "camel-accounts: missing PKCE verifier"; happens if storage is cleared mid-flow.
  • CORS: browser calls to /oauth/token and /oauth/userinfo from another origin require that origin in the server's CORS_ALLOWED_ORIGINS.

Related