Kevo Docs

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.

typescript
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

typescript
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

http
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.

typescript
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()
Social auth requires the popup to complete. Make sure your browser does not block popups for your domain. The popup resolves by posting a message back to the opener.
Kevo stores provider profile metadata when available. Google returns email, name, and picture. X returns username, name, and profile image, but does not provide email through the standard profile endpoint. Apple returns email and name only when Apple includes them in the Sign in with Apple payload.

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/callback as 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.

typescript
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.

typescript
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

typescript
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

typescript
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.

typescript
// Development
<KevoProvider config={{ publishableKey: 'pk_test_xxxxxxxxxxxx' }} />

// Production
<KevoProvider config={{ publishableKey: 'pk_live_xxxxxxxxxxxx' }} />