@digibuffer/cone-auth
v0.1.8
Published
ConeID OAuth flow utilities and React components for Cone ecosystem apps.
Readme
@digibuffer/cone-auth
ConeID OAuth flow utilities and React components for Cone ecosystem apps (StorageCone, ConeMotion, ConeFrame, etc.).
Handles the common problem where email verification in a new tab causes a PKCE mismatch on the original tab, resulting in an AccessDenied or OAuthCallbackError. The package auto-retries the sign-in silently so users never see the error.
Installation
npm install @digibuffer/cone-authPeer dependencies — already in your app, no extra install needed:
next-auth(any version)react^18 or ^19
Setup (3 steps)
1. Before calling signIn() — mark the flow as started
Call startConeOAuthFlow() right before signIn() on your login and signup pages.
// app/(auth)/login/page.tsx
import { startConeOAuthFlow } from "@digibuffer/cone-auth/ui"
import { signIn } from "next-auth/react"
function handleSignIn() {
startConeOAuthFlow() // ← sets a timestamp flag in sessionStorage
signIn("coneid", { callbackUrl: "/dashboard" })
}// app/(auth)/signup/page.tsx
import { startConeOAuthFlow } from "@digibuffer/cone-auth/ui"
import { signIn } from "next-auth/react"
function handleSignUp() {
startConeOAuthFlow()
signIn("coneid", { callbackUrl: "/dashboard" }, { prompt: "create" })
}2. On the login page — use the retry hook
useConeOAuthRetry automatically retries sign-in (up to 2 times) when the user lands back on the login page mid-flow — whether there's an error param in the URL or not.
// app/(auth)/login/page.tsx
"use client"
import { useSearchParams } from "next/navigation"
import { signIn } from "next-auth/react"
import { useConeOAuthRetry, startConeOAuthFlow } from "@digibuffer/cone-auth/ui"
function LoginPage() {
const searchParams = useSearchParams()
const callbackUrl = searchParams.get("callbackUrl") || "/dashboard"
const errorParam = searchParams.get("error")
const { isRetrying, showRetryBanner, retry } = useConeOAuthRetry({
providerId: "coneid", // your next-auth provider id
callbackUrl,
errorParam, // pass the raw error from the URL
})
const handleSignIn = () => {
startConeOAuthFlow()
signIn("coneid", { callbackUrl })
}
// Show spinner while auto-retry is in flight
if (isRetrying) {
return <Spinner />
}
return (
<div>
{/* Show a banner when retries are exhausted */}
{showRetryBanner && (
<p>Sign-in session expired. Please try again.</p>
)}
<button onClick={showRetryBanner ? retry : handleSignIn}>
Sign in with Cone ID
</button>
</div>
)
}Hook options
| Option | Type | Default | Description |
|---|---|---|---|
| providerId | string | "coneid" | The next-auth provider id |
| callbackUrl | string | "/dashboard" | Where to redirect after sign-in |
| errorParam | string \| null | — | Value of ?error= from the URL |
Hook return values
| Value | Type | Description |
|---|---|---|
| isRetrying | boolean | true while auto-retry is in flight — show a spinner |
| showRetryBanner | boolean | true when retries are exhausted — show a manual retry UI |
| retry | () => void | Call this when the user manually clicks "Try again" |
3. In the authenticated area — clear the flags
Mount <ConeAuthFlowCleanup /> anywhere in your dashboard/authenticated layout. It clears the sessionStorage flags so logging out doesn't accidentally trigger an auto-retry on the next visit to the login page.
// app/dashboard/layout.tsx
import { ConeAuthFlowCleanup } from "@digibuffer/cone-auth/ui"
export default function DashboardLayout({ children }) {
return (
<div>
<ConeAuthFlowCleanup /> {/* ← clears flags on mount */}
{children}
</div>
)
}Or call clearConeOAuthFlow() directly if you have a client component that already mounts in the authenticated area:
import { clearConeOAuthFlow } from "@digibuffer/cone-auth"
useEffect(() => {
clearConeOAuthFlow()
}, [])How it works
startConeOAuthFlow()stores a timestamp insessionStoragebefore the OAuth redirect.- When the user returns to the login page (with or without an error param),
useConeOAuthRetrychecks:- Is there a known retryable OAuth error in the URL? (
AccessDenied,OAuthCallbackError, etc.) - Or is there a recent flow timestamp in sessionStorage (within 15 minutes)?
- Is there a known retryable OAuth error in the URL? (
- If either is true and retries haven't been exhausted (max 2), it silently calls
signIn()again — the user sees only a spinner. - On the second try the PKCE/state cookies are fresh, so it succeeds.
- Once the user reaches the dashboard,
clearConeOAuthFlow()removes the flags.
Exports
@digibuffer/cone-auth (logic only, no React)
import {
startConeOAuthFlow, // call before signIn()
clearConeOAuthFlow, // call on dashboard mount
isConeOAuthInProgress, // returns true if a flow is active
checkConeOAuthRetry, // returns 'retry' | 'banner' | 'none'
CONE_RETRYABLE_ERRORS, // Set of retryable error codes
CONE_AUTH_FLOW_KEY, // sessionStorage key name
CONE_AUTH_RETRY_KEY, // sessionStorage retry count key
CONE_AUTH_MAX_RETRIES, // 2
CONE_AUTH_FLOW_TTL_MS, // 15 minutes in ms
} from "@digibuffer/cone-auth"@digibuffer/cone-auth/ui (React, requires next-auth peer dep)
import {
useConeOAuthRetry, // hook for the login page
startConeOAuthFlow, // re-exported for convenience
ConeSignInButton, // ready-made sign-in button component
ConeAuthFlowCleanup, // component to mount in dashboard layout
} from "@digibuffer/cone-auth/ui"<ConeSignInButton /> props
| Prop | Type | Default | Description |
|---|---|---|---|
| providerId | string | "coneid" | next-auth provider id |
| callbackUrl | string | "/dashboard" | Redirect after sign-in |
| label | string | "Sign in with Cone ID" | Button text |
| loadingLabel | string | "Redirecting to Cone ID…" | Text while loading |
| className | string | — | CSS class for the button |
| onSignIn | () => void | — | Called after sign-in is triggered |
License
UNLICENSED — published publicly for transparency. Use is permitted only within applications authorized by Cone.
