The support inbox lists customer tickets for the signed-in agent. The home page stays reachable without a session.
Clone starters/remix. SDK reference: Remix.
The ticket list answers who the agent is. It does not grant a permission. Apply authorization after the identity check.
What you need
| Requirement | Detail |
|---|---|
| Public key | pk_... from the console |
| Environment | Copy .env.example to .env and set PK_AUTHDOG |
| Secret key | Never put sk_... in client code |
| Return URL | Account portal back to this app. Local: http://localhost:5173. The root loader must receive ?token= |
Run
npm install
npm run devOpen http://localhost:5173. Sign in, then open /inbox. You should see tkt_88 (Northwind, invoice PDF) and tkt_89 (Contoso, locked test user). Without a session, /inbox redirects to signinUri.
Root loader
identityLoader() validates ?token= through userinfo and sets HttpOnly cookies. Keep that exchange on the root loader. Calling .json() on the callback response and rebuilding it drops Set-Cookie.
// app/root.tsx
import { Outlet, Scripts } from "@remix-run/react"
import { AuthdogProvider, ReloadPage } from "@authdog/remix-node/client"
import { identityLoader } from "@authdog/remix-node"
export const loader = identityLoader()
export default function App() {
return (
<AuthdogProvider>
<Outlet />
<Scripts />
<ReloadPage />
</AuthdogProvider>
)
}AuthdogProvider strips ?token= in the browser. The HttpOnly cookie is set by the server loader. The loader itself returns an unauthenticated result instead of denying the request. The inbox enforces it.
Inbox
// app/routes/inbox.tsx
import { identityLoader } from "@authdog/remix-node"
import { redirect, type LoaderFunctionArgs } from "@remix-run/node"
const TICKETS = [
{ id: "tkt_88", from: "Northwind", subject: "Invoice PDF missing", state: "open" },
{ id: "tkt_89", from: "Contoso", subject: "SSO test user locked", state: "waiting" },
]
const loadIdentity = identityLoader()
export const loader = async (args: LoaderFunctionArgs) => {
const response = await loadIdentity(args)
if (!(response instanceof Response)) {
throw new Response("Identity loader failed", { status: 500 })
}
const identity = (await response.json()) as {
isAuthenticated: boolean
signinUri: string
}
if (!identity.isAuthenticated) throw redirect(identity.signinUri)
return { ...identity, tickets: TICKETS }
}