> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend Wallet Verification

> Verify OMS Wallet ID tokens on your backend against the staging JWKS.

Verify a non-custodial wallet user on your backend by accepting an ID token from the client and checking its signature and claims against the staging issuer.

<Note>
  Start with the quickstart for your SDK first: [TypeScript](/wallets/sdk/typescript/quickstart), [React Native](/wallets/sdk/react-native/quickstart), [Swift](/wallets/sdk/swift/quickstart), or [Kotlin](/wallets/sdk/kotlin/quickstart).
</Note>

<Warning>
  This guide is for the staging issuer at `https://d26giflyqapd29.cloudfront.net`. Use the production issuer and JWKS URL when verifying production tokens.
</Warning>

## 1. Send the ID token to your backend

After the user authenticates in the app, request an ID token for the active wallet. Send only the returned `idToken` string to your backend over HTTPS.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await oms.wallet.startEmailAuth({ email: 'user@example.com' })
    await oms.wallet.completeEmailAuth({ code: '123456' })

    const idToken = await oms.wallet.getIdToken({
      ttlSeconds: 300,
    })

    await fetch('/api/wallet-session', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ idToken }),
    })
    ```
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    import {
      completeEmailAuth,
      getIdToken,
      startEmailAuth,
    } from '@0xsequence/oms-react-native-sdk'

    await startEmailAuth('user@example.com')
    await completeEmailAuth({ code: '123456' })

    const idToken = await getIdToken({
      ttlSeconds: 300,
    })

    await fetch('https://api.example.com/wallet-session', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ idToken }),
    })
    ```
  </Tab>

  <Tab title="Swift">
    ```swift theme={null}
    try await oms.wallet.startEmailAuth(email: "user@example.com")
    try await oms.wallet.completeEmailAuth(code: "123456")

    let idToken = try await oms.wallet.getIdToken(ttlSeconds: 300)

    var request = URLRequest(url: URL(string: "https://api.example.com/wallet-session")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "content-type")
    request.httpBody = try JSONEncoder().encode(["idToken": idToken])

    _ = try await URLSession.shared.data(for: request)
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    import com.omsclient.kotlin_sdk.wallet.CompleteAuthResult
    import kotlinx.serialization.encodeToString
    import kotlinx.serialization.json.Json
    import kotlinx.serialization.json.buildJsonObject
    import kotlinx.serialization.json.put
    import okhttp3.MediaType.Companion.toMediaType
    import okhttp3.OkHttpClient
    import okhttp3.Request
    import okhttp3.RequestBody.Companion.toRequestBody

    val okHttpClient = OkHttpClient()

    client.wallet.startEmailAuth("user@example.com")
    val auth = client.wallet.completeEmailAuth("123456")
    check(auth is CompleteAuthResult.WalletSelected)

    val idToken = client.wallet.getIdToken(ttlSeconds = 300u)
    val body = Json.encodeToString(
        buildJsonObject {
            put("idToken", idToken)
        },
    )

    val request = Request.Builder()
        .url("https://api.example.com/wallet-session")
        .post(body.toRequestBody("application/json".toMediaType()))
        .build()

    okHttpClient.newCall(request).execute().use { response ->
        check(response.isSuccessful)
    }
    ```
  </Tab>
</Tabs>

## 2. Verify the token signature and claims

Fetch keys from the staging JWKS endpoint and use a JWT library to verify the token. The library should select the signing key from the JWT header `kid`.

For staging, validate:

| Claim | Expected value                          |
| ----- | --------------------------------------- |
| `iss` | `https://d26giflyqapd29.cloudfront.net` |
| `aud` | Contains your OMS project ID            |
| `exp` | Is not in the past                      |

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { createRemoteJWKSet, jwtVerify } from 'jose'

    const STAGING_ISSUER = 'https://d26giflyqapd29.cloudfront.net'
    const STAGING_JWKS = new URL(`${STAGING_ISSUER}/.well-known/jwks.json`)
    const jwks = createRemoteJWKSet(STAGING_JWKS)

    type VerifiedWalletUser = {
      walletAddress: string
      walletType: 'ethereum'
      walletId: string
      email?: string
    }

    export async function verifyWalletIdToken(
      idToken: string,
      projectId: string,
    ): Promise<VerifiedWalletUser> {
      const { payload } = await jwtVerify(idToken, jwks, {
        issuer: STAGING_ISSUER,
        audience: projectId,
      })

      if (payload.wallet_type !== 'ethereum') {
        throw new Error('Unexpected wallet_type claim')
      }

      if (typeof payload.wallet_address !== 'string' || payload.wallet_address.length === 0) {
        throw new Error('Missing wallet_address claim')
      }

      if (typeof payload.sub !== 'string' || payload.sub.length === 0) {
        throw new Error('Missing sub claim')
      }

      return {
        walletAddress: payload.wallet_address,
        walletType: payload.wallet_type,
        walletId: payload.sub,
        email: typeof payload.email === 'string' ? payload.email : undefined,
      }
    }
    ```
  </Tab>
</Tabs>

## 3. Use the wallet claims

After verification succeeds, trust the claims from the verified JWT payload for the current request.

| Claim            | Description                             |
| ---------------- | --------------------------------------- |
| `wallet_address` | User's wallet address.                  |
| `wallet_type`    | Wallet type. This is always `ethereum`. |
| `sub`            | Internal wallet ID.                     |
| `email`          | User's email address, when available.   |

<Tabs>
  <Tab title="Express">
    ```typescript theme={null}
    app.post('/api/wallet-session', async (req, res) => {
      const idToken = req.body.idToken
      const projectId = process.env.OMS_PROJECT_ID

      if (typeof idToken !== 'string' || !projectId) {
        res.status(400).json({ error: 'Missing token or project ID' })
        return
      }

      try {
        const user = await verifyWalletIdToken(idToken, projectId)

        res.json({
          walletAddress: user.walletAddress,
          walletId: user.walletId,
          email: user.email ?? null,
        })
      } catch {
        res.status(401).json({ error: 'Invalid wallet token' })
      }
    })
    ```
  </Tab>
</Tabs>
