Email & Social Authentication
Kevo supports passwordless email OTP and OAuth social logins out of the box. All flows can be triggered via the pre-built modal or programmatically through the SDK, with profile metadata exposed when the provider returns it.
Using the Modal
The easiest way to add authentication is to use KevoModal. It shows only the auth methods enabled in your project settings. No code changes required when you enable or disable methods in the portal.
import { KevoModal, useKevoModal } from '@kevo-ws/sdk/react'
function App() {
const { open } = useKevoModal()
return (
<>
<KevoModal />
<button onClick={open}>Sign In</button>
</>
)
}Email OTP
Kevo sends a one-time code to the user's email. The flow is two steps: request the code, then verify it.
Programmatic flow
import { useKevo } from '@kevo-ws/sdk/react'
function EmailSignIn() {
const { sendEmailOtp, verifyEmailOtp } = useKevo()
const [email, setEmail] = useState('')
const [code, setCode] = useState('')
const [codeSent, setCodeSent] = useState(false)
const requestCode = async () => {
await sendEmailOtp(email)
setCodeSent(true)
}
const verify = async () => {
await verifyEmailOtp(email, code)
// user is now signed in, useKevo().isAuthenticated will update
}
if (!codeSent) {
return (
<form onSubmit={(e) => { e.preventDefault(); requestCode() }}>
<input value={email} onChange={(e) => setEmail(e.target.value)} type="email" />
<button type="submit">Send Code</button>
</form>
)
}
return (
<form onSubmit={(e) => { e.preventDefault(); verify() }}>
<input value={code} onChange={(e) => setCode(e.target.value)} maxLength={6} />
<button type="submit">Verify</button>
</form>
)
}API endpoints
POST /v1/auth/send-otp
Content-Type: application/json
{ "email": "[email protected]", "projectId": "uuid", "publishableKey": "pk_live_..." }
---
POST /v1/auth/verify-otp
Content-Type: application/json
{ "email": "[email protected]", "code": "123456", "projectId": "uuid", "publishableKey": "pk_live_..." }
# Response:
{
"accessToken": "eyJ...",
"expiresIn": 900
}
# Plus: Set-Cookie: kevo_refresh_token=...Social Auth (Google, X, Apple)
Social auth uses OAuth. When called programmatically, Kevo opens a popup window to handle the OAuth flow, then closes it and resolves the session.
import { useKevo } from '@kevo-ws/sdk/react'
const { loginWithGoogle, loginWithX, loginWithApple } = useKevo()
// Open Google OAuth redirect
loginWithGoogle()
// Open X (Twitter) OAuth redirect
loginWithX()
// Open Apple Sign In redirect
loginWithApple()Configuring OAuth in the portal
For each social provider, you need to set the OAuth credentials in the Kevo Portal:
- Google: Create OAuth 2.0 credentials in Google Cloud Console. Add
https://api.kevo.ws/v1/auth/google/callbackas an authorized redirect URI. - X (Twitter): Create a developer app at developer.twitter.com with OAuth 1.0a enabled.
- Apple: Create a Service ID in Apple Developer with Sign In with Apple enabled.
User Profile & Linked Email
Use useUserProfile() to render a consistent user identity in your app. The resolved profile always exposes a display name based on the wallet address, a provider username/name when available, and an avatar URL. If the social provider does not return an avatar, Kevo generates a deterministic avatar from the wallet address.
import { useUserProfile } from '@kevo-ws/sdk/react'
function AccountBadge() {
const { profile, rawProfile } = useUserProfile()
return (
<div>
<img src={profile.avatarUrl} alt="" />
<strong>{profile.name}</strong> {/* e.g. 0x1234…abcd */}
{profile.username && <span>{profile.username}</span>}
{rawProfile?.email && <span>{rawProfile.email}</span>}
</div>
)
}Linking an email after social login
Private key export always requires email verification. If a user signs in with X, Apple, passkey, or an external wallet and no email is available, ask them to link an email before export.
const { requestEmailLink, confirmEmailLink } = useUserProfile()
await requestEmailLink('[email protected]') // sends 6-digit OTP
await confirmEmailLink('[email protected]', '123456')Session Lifecycle
Access token
After successful auth, Kevo issues a short-lived access token (JWT, 15 minutes) and sets an HttpOnly refresh token cookie (7 days).
The SDK stores the access token in memory and automatically refreshes it using the cookie when it expires. You don't need to manage token refresh manually.
KevoSession object
interface KevoSession {
accessToken: string // Bearer token for API requests
expiresAt: number // Unix timestamp (ms) when the access token expires
userId: string // Kevo internal user ID
did: string // Decentralized identifier (project-scoped)
projectId: string
}Manual refresh
import { useKevo } from '@kevo-ws/sdk/react'
const { client, logout } = useKevo()
// Force a token refresh (called automatically by the SDK)
await client.refreshSession()
// Sign out (clears session and cookie)
await logout()Test Mode
Each project has a test publishable key (prefixed pk_test_). Test keys work identically to live keys but are restricted to localhost origins. Use them during development so test users don't count against your MAU quota.
// Development
<KevoProvider config={{ publishableKey: 'pk_test_xxxxxxxxxxxx' }} />
// Production
<KevoProvider config={{ publishableKey: 'pk_live_xxxxxxxxxxxx' }} />