@smarthivelabs-devs/auth-react
v1.4.0
Published
SmartHive Auth React provider, hooks, and components for web applications
Readme
@smarthivelabs-devs/auth-react
React provider, hooks, and components for SmartHive Auth. Works with any React 18+ app including Next.js (App Router and Pages Router), Vite, and Create React App.
Installation
npm install @smarthivelabs-devs/auth-react @smarthivelabs-devs/auth-sdk
# or
pnpm add @smarthivelabs-devs/auth-react @smarthivelabs-devs/auth-sdk
# or
yarn add @smarthivelabs-devs/auth-react @smarthivelabs-devs/auth-sdk
@smarthivelabs-devs/auth-sdkis a required peer dependency.
Prerequisites
From your SmartHive dashboard, get:
projectIdpublishableKeybaseUrl— the URL of your SmartHive Auth service
Setup
Wrap your app with SmartHiveAuthProvider. This provides auth state to every component in the tree.
Next.js App Router
Create a client-side provider wrapper (the SDK uses React context, which requires "use client"):
// app/providers.tsx
"use client";
import { SmartHiveAuthProvider } from "@smarthivelabs-devs/auth-react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SmartHiveAuthProvider
projectId={process.env.NEXT_PUBLIC_AUTH_PROJECT_ID!}
publishableKey={process.env.NEXT_PUBLIC_AUTH_PUBLISHABLE_KEY!}
baseUrl={process.env.NEXT_PUBLIC_AUTH_BASE_URL!}
redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`}
>
{children}
</SmartHiveAuthProvider>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Vite / Create React App
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { SmartHiveAuthProvider } from "@smarthivelabs-devs/auth-react";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<SmartHiveAuthProvider
projectId={import.meta.env.VITE_AUTH_PROJECT_ID}
publishableKey={import.meta.env.VITE_AUTH_PUBLISHABLE_KEY}
baseUrl={import.meta.env.VITE_AUTH_BASE_URL}
redirectUri={`${window.location.origin}/auth/callback`}
>
<App />
</SmartHiveAuthProvider>
</StrictMode>
);Headless Sign-in (Custom Login Form)
No browser redirect. Call the method, get tokens. Full control of your UI.
Email + Password
import { useAuth } from "@smarthivelabs-devs/auth-react";
import { useState } from "react";
export default function LoginPage() {
const { signIn } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
try {
await signIn.email({ email, password });
// Session saved automatically — user is now signed in
} catch (e: any) {
setError(e.message);
}
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" type="email" />
<input value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" type="password" />
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Sign in</button>
</form>
);
}Phone OTP
const { signIn } = useAuth();
// Step 1 — send the code
await signIn.phone.sendOtp({ phoneNumber: "+1234567890" });
// Step 2 — verify (returns session, user is now signed in)
await signIn.phone.verify({ phoneNumber: "+1234567890", code: "123456" });Email OTP
const { signIn } = useAuth();
// Step 1 — send the code
await signIn.emailOtp.send({ email: "[email protected]" });
// Step 2 — verify (returns session, user is now signed in)
await signIn.emailOtp.verify({ email: "[email protected]", code: "123456" });Magic Link
const { signIn } = useAuth();
// Sends an email — user clicks the link to sign in (no token returned here)
await signIn.magicLink.send({ email: "[email protected]" });Headless Sign-up
const { signUp } = useAuth();
const result = await signUp.email({
email: "[email protected]",
password: "secret123",
name: "Jane Doe", // optional
});
if (result.requiresVerification) {
// Email verification required — show "check your inbox" screen
} else {
// Account created and signed in immediately
}Social OAuth Sign-in (Google, Apple, GitHub, etc.)
Each provider uses your project's own credentials — the consent screen shows your app name. Configure credentials in your SmartHive dashboard under Project → OAuth Providers, then use loginSocial:
import { useAuth } from "@smarthivelabs-devs/auth-react";
export function SocialLoginButtons() {
const { loginSocial } = useAuth();
return (
<div>
<button onClick={() => loginSocial("google")}>Continue with Google</button>
<button onClick={() => loginSocial("apple")}>Continue with Apple</button>
<button onClick={() => loginSocial("github")}>Continue with GitHub</button>
</div>
);
}Set up a callback page that your redirectUri points to. It reads access_token + refresh_token from the URL and saves the session:
// app/auth/social/callback/page.tsx
"use client";
import { useRouter } from "next/navigation";
import { SocialAuthCallback } from "@smarthivelabs-devs/auth-react";
export default function SocialCallback() {
const router = useRouter();
return (
<SocialAuthCallback
onSuccess={() => router.replace("/dashboard")}
onError={(err) => router.replace(`/login?error=${err.message}`)}
fallback={<p>Signing you in...</p>}
/>
);
}Pass the callback URL when initiating:
loginSocial("google", { redirectUri: "https://myapp.com/auth/social/callback" });Supported providers:
google · apple · github · facebook · twitter · linkedin · microsoft · discord · spotify · twitch · reddit · gitlab · slack · notion · zoom · figma
OAuth Redirect Sign-in (SmartHive hosted page)
The original PKCE flow — redirects to the SmartHive hosted login page and back.
import { useAuth } from "@smarthivelabs-devs/auth-react";
export function LoginButton() {
const { login } = useAuth();
return <button onClick={() => login()}>Sign in with SmartHive</button>;
}Set up a callback page to handle the redirect back:
// app/auth/callback/page.tsx
"use client";
import { useRouter } from "next/navigation";
import { AuthCallback } from "@smarthivelabs-devs/auth-react";
export default function Callback() {
const router = useRouter();
return (
<AuthCallback
onSuccess={() => router.replace("/dashboard")}
onError={() => router.replace("/login")}
fallback={<div>Signing you in...</div>}
/>
);
}Provider Props
SmartHiveAuthProvider accepts all SmartHiveAuthConfig fields as props:
| Prop | Type | Required | Description |
|---|---|---|---|
| projectId | string | Yes | Your SmartHive project ID |
| publishableKey | string | Yes | Your publishable (public) key |
| baseUrl | string | Yes | URL of your SmartHive Auth service |
| authDomain | string | No | Custom branded auth domain for login flows |
| redirectUri | string | No | Default OAuth callback URL |
| children | ReactNode | Yes | Your app tree |
Hooks
useAuth()
Returns the full auth context. Use this when you need multiple values at once.
import { useAuth } from "@smarthivelabs-devs/auth-react";
function MyComponent() {
const {
client, // SmartHiveAuthClient — the raw SDK client
session, // AuthSession | null
isLoaded, // true once the initial session check is done
isSignedIn, // boolean
login, // PKCE redirect sign-in (SmartHive hosted page)
loginSocial, // social OAuth redirect (Google, Apple, GitHub, etc.)
logout, // () => Promise<void>
refreshSession, // () => Promise<void>
getAuthorizationHeader, // () => Promise<Record<string, string>>
authFetch, // fetch wrapper that adds the Bearer token
signIn, // headless sign-in methods (no redirect)
signUp, // headless sign-up methods (no redirect)
} = useAuth();
if (!isLoaded) return <p>Loading...</p>;
return isSignedIn ? (
<button onClick={logout}>Sign out</button>
) : (
<button onClick={() => login()}>Sign in</button>
);
}useSession()
Returns the current AuthSession or null.
import { useSession } from "@smarthivelabs-devs/auth-react";
function TokenDisplay() {
const session = useSession();
if (!session) return null;
return <pre>{session.accessToken}</pre>;
}useUser()
Returns the current user object from the session, or null.
import { useUser } from "@smarthivelabs-devs/auth-react";
function UserGreeting() {
const user = useUser();
if (!user) return null;
return <p>Welcome, {(user as any).email}</p>;
}useIsLoaded()
Returns true once the initial session check has finished. Use this to avoid flash of unauthenticated content.
import { useIsLoaded } from "@smarthivelabs-devs/auth-react";
function App() {
const isLoaded = useIsLoaded();
if (!isLoaded) return <FullPageSpinner />;
return <Router />;
}useIsSignedIn()
Returns true if signed in, false if signed out, or null while still loading.
import { useIsSignedIn } from "@smarthivelabs-devs/auth-react";
function NavBar() {
const isSignedIn = useIsSignedIn();
if (isSignedIn === null) return <NavSkeleton />;
return isSignedIn ? <UserMenu /> : <LoginButton />;
}useAuthFetch()
Returns an authenticated fetch function that automatically includes the Bearer token on every request.
import { useAuthFetch } from "@smarthivelabs-devs/auth-react";
function DataLoader() {
const authFetch = useAuthFetch();
async function loadData() {
const res = await authFetch("/api/protected-data");
const data = await res.json();
console.log(data);
}
return <button onClick={loadData}>Load data</button>;
}useAuthorizationHeader()
Returns a function that resolves to { authorization: "Bearer <token>" }. Useful for manually constructing requests or passing to third-party clients.
import { useAuthorizationHeader } from "@smarthivelabs-devs/auth-react";
function UploadButton() {
const getAuthorizationHeader = useAuthorizationHeader();
async function upload(file: File) {
const headers = await getAuthorizationHeader();
const formData = new FormData();
formData.append("file", file);
await fetch("/api/upload", { method: "POST", headers, body: formData });
}
return <input type="file" onChange={(e) => e.target.files && upload(e.target.files[0])} />;
}Render Helpers
Conditional rendering components based on auth state. These are ergonomic wrappers that also wait for isLoaded.
<SignedIn>
Renders children only when auth has loaded and a session exists.
import { SignedIn } from "@smarthivelabs-devs/auth-react";
<SignedIn>
<UserDashboard />
</SignedIn><SignedOut>
Renders children only when auth has loaded and there is no session.
import { SignedOut } from "@smarthivelabs-devs/auth-react";
<SignedOut>
<LandingPage />
</SignedOut><AuthLoading>
Renders children while the initial session check is in progress.
import { AuthLoading } from "@smarthivelabs-devs/auth-react";
<AuthLoading>
<FullPageSpinner />
</AuthLoading>Combined Example
import { SignedIn, SignedOut, AuthLoading } from "@smarthivelabs-devs/auth-react";
export default function HomePage() {
return (
<>
<AuthLoading>
<p>Checking authentication...</p>
</AuthLoading>
<SignedIn>
<Dashboard />
</SignedIn>
<SignedOut>
<LandingPage />
</SignedOut>
</>
);
}<AuthCallback> Component
Handles the OAuth redirect on your callback page. Automatically exchanges the code for tokens and calls onSuccess when done.
// app/auth/callback/page.tsx (Next.js App Router)
"use client";
import { useRouter } from "next/navigation";
import { AuthCallback } from "@smarthivelabs-devs/auth-react";
export default function CallbackPage() {
const router = useRouter();
return (
<AuthCallback
onSuccess={() => router.replace("/dashboard")}
onError={(err) => {
console.error("Auth error:", err.message);
router.replace("/login?error=auth_failed");
}}
fallback={<p>Completing sign in...</p>}
/>
);
}AuthCallback Props
| Prop | Type | Required | Description |
|---|---|---|---|
| onSuccess | (session: AuthSession) => void | Yes | Called after successful token exchange |
| onError | (error: Error) => void | No | Called if the exchange fails |
| fallback | ReactNode | No | Shown while the exchange is in progress |
<SocialAuthCallback> Component
Handles the social OAuth redirect on your callback page. Reads access_token + refresh_token from the URL, saves the session, and calls onSuccess. Use this on whatever page your social redirectUri points to.
// app/auth/social/callback/page.tsx (Next.js App Router)
"use client";
import { useRouter } from "next/navigation";
import { SocialAuthCallback } from "@smarthivelabs-devs/auth-react";
export default function SocialCallbackPage() {
const router = useRouter();
return (
<SocialAuthCallback
onSuccess={() => router.replace("/dashboard")}
onError={(err) => {
console.error("Social auth error:", err.message);
router.replace("/login?error=social_auth_failed");
}}
fallback={<p>Completing sign in...</p>}
/>
);
}SocialAuthCallback Props
| Prop | Type | Required | Description |
|---|---|---|---|
| onSuccess | (session: AuthSession) => void | Yes | Called after tokens are parsed and session saved |
| onError | (error: Error) => void | No | Called if the provider returned an error or token is missing |
| fallback | ReactNode | No | Shown while parsing the callback URL |
Route Protection
Next.js Middleware (App Router)
Protect routes server-side using @smarthivelabs-devs/auth-server:
// middleware.ts
import { createNextAuthMiddleware } from "@smarthivelabs-devs/auth-server";
const { nextAuthMiddleware } = createNextAuthMiddleware({
issuer: process.env.AUTH_ISSUER!,
projectId: process.env.AUTH_PROJECT_ID,
});
export async function middleware(request: Request) {
const { auth, error } = await nextAuthMiddleware(request);
if (error) {
return Response.redirect(new URL("/login", request.url));
}
// auth.userId is available
}
export const config = {
matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};Client-side Guard (React)
"use client";
import { useIsSignedIn, useIsLoaded } from "@smarthivelabs-devs/auth-react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export function ProtectedPage({ children }: { children: React.ReactNode }) {
const isLoaded = useIsLoaded();
const isSignedIn = useIsSignedIn();
const router = useRouter();
useEffect(() => {
if (isLoaded && !isSignedIn) {
router.replace("/login");
}
}, [isLoaded, isSignedIn, router]);
if (!isLoaded) return <FullPageSpinner />;
if (!isSignedIn) return null;
return <>{children}</>;
}Sign-out
const { logout } = useAuth();
// Clears localStorage + invalidates session on the server
await logout();Full Next.js App Router Example
app/
├── layout.tsx ← SmartHiveAuthProvider here
├── page.tsx ← uses SignedIn / SignedOut
├── login/
│ └── page.tsx ← custom login form using signIn.*
├── auth/
│ └── callback/
│ └── page.tsx ← AuthCallback (OAuth redirect only)
└── dashboard/
└── page.tsx ← protected page// app/login/page.tsx — custom form, no redirect
"use client";
import { useAuth } from "@smarthivelabs-devs/auth-react";
import { useState } from "react";
export default function LoginPage() {
const { signIn } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
try {
await signIn.email({ email, password });
} catch (e: any) {
setError(e.message ?? "Sign in failed.");
}
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="Email" />
<input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="Password" />
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Sign in</button>
</form>
);
}// app/dashboard/page.tsx
"use client";
import { useAuth, SignedIn } from "@smarthivelabs-devs/auth-react";
export default function Dashboard() {
const { logout, authFetch } = useAuth();
async function fetchProfile() {
const res = await authFetch("/api/profile");
console.log(await res.json());
}
return (
<SignedIn>
<h1>Dashboard</h1>
<button onClick={fetchProfile}>Load profile</button>
<button onClick={logout}>Sign out</button>
</SignedIn>
);
}Environment Variables
# .env.local (Next.js)
NEXT_PUBLIC_AUTH_PROJECT_ID=proj_abc123
NEXT_PUBLIC_AUTH_PUBLISHABLE_KEY=pk_live_abc123
NEXT_PUBLIC_AUTH_BASE_URL=https://auth.myapp.com
NEXT_PUBLIC_APP_URL=https://myapp.com# .env (Vite)
VITE_AUTH_PROJECT_ID=proj_abc123
VITE_AUTH_PUBLISHABLE_KEY=pk_live_abc123
VITE_AUTH_BASE_URL=https://auth.myapp.comTypeScript Types
import type {
SmartHiveAuthProviderProps,
SocialProvider, // "google" | "apple" | "github" | ...
} from "@smarthivelabs-devs/auth-react";
import type {
AuthSession,
HeadlessClient,
HeadlessSignInResult,
HeadlessSignUpResult,
SmartHiveAuthClient,
SmartHiveAuthConfig,
} from "@smarthivelabs-devs/auth-sdk";Related Packages
| Package | Use case |
|---|---|
| @smarthivelabs-devs/auth-sdk | Core SDK — framework-agnostic |
| @smarthivelabs-devs/auth-expo | React Native / Expo apps |
| @smarthivelabs-devs/auth-server | Express / Next.js server-side JWT verification |
License
MIT © SmartHive Labs
