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 || ^19andreact-dom - Browser support: requires Web Crypto (
crypto.subtle) andpostMessage
npm install camel-accounts-reactExports
| Export | Kind | Description |
|---|---|---|
CamelAuthProvider | component | Wraps your app and provides auth state/actions |
CamelAuthContext | context | Raw React context (usually via useCamelAuth) |
CamelLoginButton | component | Google-style "Sign in with Camel Accounts" button |
useCamelAuth | hook | Reads auth state + actions; errors if used outside the provider |
CamelAuthConfig | type | Provider config |
CamelAuthTokens | type | Token pair returned after login |
CamelAuthUser | type | Userinfo claims |
AuthState | type | Authentication 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
| Prop | Type | Default | Description |
|---|---|---|---|
config.issuer | string | (required) | Camel Accounts base URL, e.g. https://accounts.camelcreatives.com |
config.clientId | string | (required) | Your registered client_id |
config.scopes | string[] | ["openid", "profile", "email"] | OAuth scopes requested on login |
config.redirectPath | string | "/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.
| Property | Type | Description |
|---|---|---|
user | CamelAuthUser | null | Claims fetched from /oauth/userinfo |
tokens | CamelAuthTokens | null | { accessToken, refreshToken, expiresIn, scope } |
authenticated | boolean | true while an access token exists |
loading | boolean | true during initial token restoration (localStorage mode) |
login | () => void | Opens the popup OAuth flow |
logout | () => void | Clears local tokens + user; closes any popup |
getAccessToken | () => string | null | Returns the current access token for API calls |
CamelLoginButton
| Prop | Type | Default | Description |
|---|---|---|---|
onClick | () => void | — | Handler; pass login from useCamelAuth |
disabled | boolean | false | Disables the button |
label | string | "Sign in with Camel Accounts" | Button text |
logo | string | (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
login()generates a PKCE verifier/challenge (S256) and a random CSRFstate.- PKCE verifier + state are stashed in
localStorage(orsessionStorageas a fallback). - A 500×600 centered popup opens
/oauth/authorize?response_type=code&...&code_challenge=...&state=.... - The user signs in and consents inside the popup (Camel Accounts' own UI).
- The popup lands on
/widget-callbackandpostMessages{ type: "camel-auth-callback", code, state }to the opener. - The SDK verifies
state(CSRF protection), then exchanges the code for tokens at/oauth/token. - 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 thecamel_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
| Mode | Persistence | Refresh | Best for |
|---|---|---|---|
"memory" (default) | React state only; lost on refresh | Off | SPAs with their own cookie session |
"localStorage" | Survives refreshes under camel_auth_tokens | On (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. useCamelAuthoutside 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/tokenand/oauth/userinfofrom another origin require that origin in the server'sCORS_ALLOWED_ORIGINS.
Related
- React SDK getting started — step-by-step setup
- Web SPA — widget vs. redirect vs. oidc-client-ts
- Backend Token Verification — verifying access tokens server-side