React
Sign in with UNET in a React app with a small provider and hook, a callback route, and a protected page. Plain React, no router assumed.
The SDK has no React code in it, and does not need any: one client, one provider, one hook. This recipe uses React 18 or 19 and any router (the examples use a plain pathname check so you can adapt them).
The client
Create the client once, outside any component:
// src/unet.ts
import { UnetId } from "@unet/id-web";
export const unet = new UnetId({
clientId: import.meta.env.VITE_UNET_CLIENT_ID,
redirectUri: `${location.origin}/auth/callback`,
scopes: ["openid", "profile", "offline_access"],
});A provider and a hook
// src/UnetProvider.tsx
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
import type { UnetUser } from "@unet/id-web";
import { unet } from "./unet";
type State = { user: UnetUser | null; loading: boolean };
const Ctx = createContext<State>({ user: null, loading: true });
export function UnetProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<State>({ user: unet.getUser(), loading: true });
useEffect(() => {
// follows sign-in, sign-out and a profile change, also made in another tab
const off = unet.onSessionChange((user) => setState({ user, loading: false }));
unet.restore().then((user) => setState({ user, loading: false }), () => setState({ user: null, loading: false }));
return off;
}, []);
return <Ctx.Provider value={state}>{children}</Ctx.Provider>;
}
export const useUnet = () => useContext(Ctx);restore() runs once at startup and returns null when nobody is signed in. If the network is down it rejects, and the example treats that as "signed out" for the moment; the tokens are kept, so the next call tries again.
The callback route
The page at /auth/callback finishes the sign-in. Guard it so that React's strict mode, which runs effects twice in development, does not use the one-time code twice:
// src/AuthCallback.tsx
import { useEffect, useRef, useState } from "react";
import { AccessDenied } from "@unet/id-web";
import { unet } from "./unet";
export function AuthCallback() {
const started = useRef(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (started.current) return;
started.current = true;
unet.handleRedirectCallback().then(
({ returnTo }) => location.replace(returnTo),
(e) => setError(e instanceof AccessDenied ? "Sign-in was cancelled." : "Sign-in failed. Please try again."),
);
}, []);
return error ? <p role="alert">{error}</p> : <p>Signing you in…</p>;
}Using it
import { unet } from "./unet";
import { useUnet } from "./UnetProvider";
export function Header() {
const { user, loading } = useUnet();
if (loading) return null;
return user ? (
<button onClick={() => void unet.signOut()}>Sign out @{user.username}</button>
) : (
<button onClick={() => void unet.signIn({ returnTo: location.pathname })}>Sign in with UNET</button>
);
}returnTo is where the person lands after the callback; the SDK keeps it for you while they are away on unet.social.
A protected page
import { useUnet } from "./UnetProvider";
import { unet } from "./unet";
export function Dashboard() {
const { user, loading } = useUnet();
if (loading) return <p>Loading…</p>;
if (!user) {
void unet.signIn({ returnTo: "/dashboard" });
return null;
}
return <h1>Hello, {user.displayName ?? user.username}</h1>;
}Server-side rendering
The SDK needs a browser. In Next.js or Remix, create the client and call its methods only in client components or effects ("use client"), never while rendering on the server.