Skip to Content
StacksPackagesUser Utils

User Utils

npm 

@stacknet/userutils is the authentication and session package for Stacknet. It provides everything you need to connect users to your Stack, Login, session management, and billing hooks.

PromptMe

Copy the prompt for your framework and give it to your AI coding agent (Claude Code, Cursor, Copilot, etc.) to scaffold Stacknet authentication into your app. Replace stk_REPLACE_ME with your Stack ID.

## Task Integrate Stacknet authentication into this Next.js app using `@stacknet/userutils`. The app should let users log in with wallet or OAuth, persist sessions via cookies, and protect pages behind an auth gate. ## Stack details - Stack ID: `stk_REPLACE_ME` - Stacknet URL: `https://stacknet.magma-rpc.com` ## Steps ### 1. Install ```bash npm install @stacknet/userutils ``` ### 2. Environment variables Add to `.env.local`: ``` NEXT_PUBLIC_STACK_ID=stk_REPLACE_ME NEXT_PUBLIC_STACKNET_URL=https://stacknet.magma-rpc.com AUTH_SECRET=generate-a-random-32-char-string STACKNET_JWT_SECRET=generate-another-random-32-char-string ``` ### 3. Create API routes Create these three Next.js App Router API routes: **`app/api/auth/callback/route.ts`** ```ts import { createAuthCallback } from '@stacknet/userutils/server' const handler = createAuthCallback({ authSecret: process.env.AUTH_SECRET || '', stacknetUrl: process.env.NEXT_PUBLIC_STACKNET_URL || 'https://stacknet.magma-rpc.com', stackId: process.env.NEXT_PUBLIC_STACK_ID || '', stacknetJwtSecret: process.env.STACKNET_JWT_SECRET, secureCookies: process.env.NODE_ENV === 'production', sessionMaxAge: 2592000, jwtExpiry: 86400, }) export const POST = handler ``` **`app/api/auth/session/route.ts`** ```ts import { createSessionHandler } from '@stacknet/userutils/server' const handler = createSessionHandler({ authSecret: process.env.AUTH_SECRET || '', secureCookies: process.env.NODE_ENV === 'production', }) export async function GET(request: Request) { return handler(request) } ``` **`app/api/auth/logout/route.ts`** ```ts import { createLogoutHandler } from '@stacknet/userutils/server' const handler = createLogoutHandler({ stacknetUrl: process.env.NEXT_PUBLIC_STACKNET_URL || 'https://stacknet.magma-rpc.com', secureCookies: process.env.NODE_ENV === 'production', }) export async function POST(request: Request) { return handler(request) } ``` ### 4. Create auth provider Create `components/auth-provider.tsx`: ```tsx 'use client' import { createContext, useContext, ReactNode } from 'react' import { UserUtilsProvider, ConnectWidget } from '@stacknet/userutils/components' import { useSession, useStackAuth } from '@stacknet/userutils/hooks' import type { UserUtilsConfig, PublicSession } from '@stacknet/userutils' const STACKNET_CONFIG: UserUtilsConfig = { apiBaseUrl: '', stackId: process.env.NEXT_PUBLIC_STACK_ID || '', stacknetUrl: process.env.NEXT_PUBLIC_STACKNET_URL || 'https://stacknet.magma-rpc.com', theme: 'dark', } interface AuthContextValue { session: PublicSession | null isAuthenticated: boolean loading: boolean logout: () => Promise<void> refresh: () => Promise<unknown> config: UserUtilsConfig } const AuthContext = createContext<AuthContextValue>({ session: null, isAuthenticated: false, loading: true, logout: async () => {}, refresh: async () => {}, config: STACKNET_CONFIG, }) export function useAuth() { return useContext(AuthContext) } function AuthInner({ children }: { children: ReactNode }) { const { session, isAuthenticated, loading, logout, refresh } = useStackAuth({ ...STACKNET_CONFIG, autoConnect: false, }) return ( <AuthContext.Provider value={{ session, isAuthenticated, loading, logout, refresh, config: STACKNET_CONFIG }}> {children} </AuthContext.Provider> ) } export function AuthProvider({ children }: { children: ReactNode }) { return ( <UserUtilsProvider config={STACKNET_CONFIG}> <AuthInner>{children}</AuthInner> </UserUtilsProvider> ) } export function LoginWidget({ onSuccess }: { onSuccess?: () => void }) { return <ConnectWidget config={STACKNET_CONFIG} onSuccess={onSuccess} showWallets={['phantom', 'metamask']} /> } export { STACKNET_CONFIG } ``` ### 5. Wrap your layout In `app/layout.tsx`, wrap your app with the `AuthProvider`: ```tsx import { AuthProvider } from '../components/auth-provider' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <AuthProvider> {children} </AuthProvider> </body> </html> ) } ``` ### 6. Create a login page Create `app/login/page.tsx`: ```tsx 'use client' import { useEffect } from 'react' import { ConnectWidget } from '@stacknet/userutils/components' import { useAuth, STACKNET_CONFIG } from '../../components/auth-provider' export default function LoginPage() { const { isAuthenticated } = useAuth() useEffect(() => { if (isAuthenticated) window.location.href = '/' }, [isAuthenticated]) if (isAuthenticated) return null return ( <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}> <ConnectWidget config={STACKNET_CONFIG} onSuccess={() => { window.location.href = '/' }} showWallets={['phantom', 'metamask']} /> </div> ) } ``` ### 7. Protect pages with auth Use the `useAuth` hook to gate content: ```tsx 'use client' import { useAuth } from '../components/auth-provider' export default function Dashboard() { const { session, isAuthenticated, loading } = useAuth() if (loading) return <p>Loading...</p> if (!isAuthenticated) { window.location.href = '/login' return null } return <h1>Welcome, {session?.address}</h1> } ``` ## Style guidelines Follow the Stacknet visual language: - **Background**: `#191919` (page), `#262625` (cards/panels) - **Text**: `#FAFAF7` (primary), `#91918D` (secondary), `#666663` (muted) - **Accent**: `#165DFC` (blue, links and primary actions) - **Danger**: `#BF4D43` (destructive actions) - **Borders**: `#40403E` - **Font**: `'Courier New', monospace` for headings and code; system sans-serif for body text - **Theme**: always dark — set `theme: 'dark'` in `UserUtilsConfig` ## Result After completing these steps you will have: - Wallet + OAuth login via Stacknet - Server-side session cookies (30-day max age, 24h JWT rotation) - A reusable `useAuth()` hook for reading session state anywhere - A login page with the `ConnectWidget` - Auth-gated pages that redirect unauthenticated users

Client side

Wrap your app with UserUtilsProvider and use hooks to read auth state:

import { UserUtilsProvider, ConnectWidget } from '@stacknet/userutils/components' import { useSession, useStackAuth } from '@stacknet/userutils/hooks' function App() { return ( <UserUtilsProvider config={{ apiBaseUrl: '/api', stackId: 'stk_YOUR_STACK_ID', stacknetUrl: 'https://stacknet.magma-rpc.com', theme: 'dark', }}> <MyApp /> </UserUtilsProvider> ) } function MyApp() { const { session, isAuthenticated } = useSession() if (!isAuthenticated) return <ConnectWidget config={config} /> return <p>Welcome, {session.address}</p> }

Server side (Next.js API routes)

Three route handlers cover the full auth lifecycle:

/api/auth/callback — verifies the signed challenge and sets the session cookie

import { createAuthCallback } from '@stacknet/userutils/server' const handler = createAuthCallback({ authSecret: process.env.AUTH_SECRET, stacknetUrl: process.env.NEXT_PUBLIC_STACKNET_URL || 'https://stacknet.magma-rpc.com', stackId: process.env.NEXT_PUBLIC_STACK_ID, stacknetJwtSecret: process.env.STACKNET_JWT_SECRET, secureCookies: process.env.NODE_ENV === 'production', sessionMaxAge: 2592000, // 30 days jwtExpiry: 86400, // 24 hours }) export const POST = handler

/api/auth/session — returns current session state

import { createSessionHandler } from '@stacknet/userutils/server' const handler = createSessionHandler({ authSecret: process.env.AUTH_SECRET, secureCookies: process.env.NODE_ENV === 'production', }) export async function GET(request: Request) { return handler(request) }

/api/auth/logout — clears the session cookie

import { createLogoutHandler } from '@stacknet/userutils/server' const handler = createLogoutHandler({ stacknetUrl: process.env.NEXT_PUBLIC_STACKNET_URL || 'https://stacknet.magma-rpc.com', secureCookies: process.env.NODE_ENV === 'production', }) export async function POST(request: Request) { return handler(request) }

Hooks reference

HookReturnsDescription
useSession{ session, isAuthenticated }Read-only session state — user ID, address, expiry
useStackAuth{ session, wallet, authenticateSolana, authenticateEVM, logout, refresh }Full auth control — connect wallets, sign challenges, manage session
useWeb3Wallet{ connected, address, chain, provider }Wallet connection state and provider detection
useAuthBridge{ ready, known, identity, identityCount, resolvedStackId }Cross-domain SSO — detects identities across Stacks
usePlans{ plans, loading }List billing plans available on the Stack
useSubscription{ subscription, active, plan }Current user subscription state
useUsage{ usage, remaining, limit }Token usage against current plan
useBillingHistory{ records, loading }Past billing and payment records
usePrepaidCheckout{ checkout, processing }Initiate credit purchases

Components reference

ComponentPropsDescription
UserUtilsProviderconfig: UserUtilsConfigRequired context provider — wrap your app root
ConnectWidgetconfig, onSuccess, showWallets, showOTP, titleDrop-in login UI with wallet and OAuth options

Environment variables

VariableRequiredDescription
NEXT_PUBLIC_STACK_IDYesYour Stack ID (e.g. stk_abc123)
NEXT_PUBLIC_STACKNET_URLYesStacknet backend URL (https://stacknet.magma-rpc.com)
AUTH_SECRETYesSecret for signing session cookies (min 32 chars)
STACKNET_JWT_SECRETYesSecret for re-signing JWTs to Stacknet backend

Cross-domain SSO

The useAuthBridge hook enables single sign-on across multiple Stacks. When a user is already authenticated on one Stack, the bridge detects their identity and can resolve which Stack they last used:

import { useAuthBridge } from '@stacknet/userutils/hooks' function LoginPage() { const bridge = useAuthBridge() if (bridge.ready && bridge.known) { return <p>Welcome back — {bridge.identity.address}</p> } return <ConnectWidget config={config} /> }
Last updated on