With a Backend
Your server handles token creation and verification. The React frontend shows the UI.
phone returned by the SSE event or onSuccess callback directly on your server. Always exchange the token server-side via verifyToken(token) — it reads the phone number from Loggin's database, making it the authoritative source. This is the same model as OAuth's authorization code flow.How it works
There are two valid flows depending on whether your server needs to know the token before the user verifies.
- Servergenerates token → stores it → sends link to frontend
- Clientshows modal with the link → user taps Send
- Servercalls
waitForVerify→ sets session
- Clientuses
@loggin/react— generates its own token in the browser - Clientuser taps Send →
onSuccessfires with phone number - Serverreceives phone from a server action → creates session
@loggin/react and call a server action from onSuccess. The decoupled flow is useful when your server needs to pre-register the token (e.g. to link it to a pending order or session record).Step 1 — Server: generate a token
Install the server SDK and call createToken with your app key. It returns a token and a pre-filled WhatsApp link. Send both to your frontend.
npm install @loggin/sdkimport { loggin } from '@loggin/sdk'
// or: const { loggin } = require('@loggin/sdk')
const { token, link } = loggin.createToken('8XKPQ2WN')
// Send `token` and `link` to your frontendpip install logginfrom loggin import Loggin
client = Loggin()
result = client.create_token('8XKPQ2WN')
# Send result.token and result.link to your frontendStep 2 — Client: show the link
The server sends token and link to the frontend. Render the link however fits your UI — a button, an anchor, or a QR code. On mobile, opening the link launches WhatsApp with the message pre-filled.
export function LoginPage({ link }: { link: string }) {
return (
<a href={link} target="_blank" rel="noopener noreferrer">
Open WhatsApp to verify
</a>
)
}@loggin/react components (LogginButton, LogginModal) generate their own token client-side — they take an appKey, not a pre-built link. Use them for the client-only flow described below, not the decoupled flow.Step 3 — Server: wait for verification (decoupled flow)
Call waitForVerify with the token. It holds an open connection and resolves the moment the user taps Send — no polling required. The resolved session contains their verified phone number.
const session = await loggin.waitForVerify(token)
// session.phone → "919876543210"
// session.appKey → "8XKPQ2WN"
// Set your session cookie / JWT heresession = client.wait_for_verify(result.token)
# session.phone → "919876543210"
# session.app_key → "8XKPQ2WN"
# Set your session cookie / JWT herewaitForVerify / wait_for_verify times out after 5 minutes and rejects / raises. Handle the error to show the user a “session expired” state.Using Next.js?
@loggin/nextjs is the easiest path for Next.js apps. It provides a verifyToken function built for server actions and a logginMiddleware helper to protect routes.
npm install @loggin/nextjsServer action
// app/actions/auth.ts
"use server"
import { verifyToken } from '@loggin/nextjs'
import { cookies } from 'next/headers'
export async function loginAction(token: string) {
const session = await verifyToken(token)
if (!session) throw new Error('Not verified yet')
const jar = await cookies()
jar.set('session', JSON.stringify({ phone: session.phone }), {
httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 24 * 30,
})
return session
}Middleware (protect routes)
// middleware.ts
import { logginMiddleware } from '@loggin/nextjs'
export default logginMiddleware({
// Redirect unauthenticated users to /login
loginPath: '/login',
// Protect everything under /dashboard
matcher: ['/dashboard/:path*'],
})
export const config = {
matcher: ['/dashboard/:path*'],
}Client-only flow with a server action
The simpler path for most apps: let @loggin/react handle the token entirely in the browser, then call a server action from onSuccess to persist the session. No createToken or waitForVerify on the server needed.
import { LogginButton } from '@loggin/react'
import { loginAction } from './actions'
export function LoginPage() {
return (
<LogginButton
appKey="8XKPQ2WN"
onSuccess={async ({ token }) => {
await loginAction(token) // set cookie, write to DB, etc.
router.push('/dashboard')
}}
/>
)
}