> For the complete documentation index, see [llms.txt](/llms.txt).

# Embedded Wallets identity token

The **Identity Token** (ID Token) issued by Embedded Wallets is a JSON Web Token (JWT) that contains verified identity claims about the authenticated user. This token is signed using Embedded Wallets' private key and cannot be spoofed, allowing developers to trust the identity information presented by the client.

Once a user successfully authenticates via Embedded Wallets, the platform issues an ID token which can then be used to authorize client-to-server requests or verify ownership of associated wallet addresses.

#### Purpose of the ID token[​](#purpose-of-the-id-token "Direct link to Purpose of the ID token")

- **User identity verification**: Ensures that the client user is indeed who they claim to be.
- **Secure backend requests**: The token should be passed in API requests to validate sessions server-side.
- **Wallet ownership proof**: Includes public wallet keys to prove a user owns a particular wallet.

When making a backend request from the frontend, the client must include this ID token to ensure the backend can verify the authenticated user.

## ID token format[​](#id-token-format "Direct link to ID token format")

Embedded Wallets (previously Web3Auth) issues tokens as ES256-signed JWTs containing various identity claims about the user.

A sample decoded token is shown below:

<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary>Sample ID Token</summary><div><div class="collapsibleContent_i85q"><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"iat"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1747727490</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"aud"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"BJp5VGbuhg_mUA.....7B0SseDPBWabmYmEFXpfu8CGWSw"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"nonce"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"030cb3f1ab9593d987b17cb....38afe331561105213"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"iss"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://api-auth.web3auth.io"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"wallets"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"public_key"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"5771379329ae0f3b76........82f17373a13d8683561"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"web3auth_app_key"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"curve"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ed25519"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"public_key"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"020fda199e933b24a74...........6c9cc67a13c23d"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"web3auth_app_key"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token string-property property">"curve"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"secp256k1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"email"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"shahbaz@web3auth.io"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Mohammad Shahbaz Alam"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"profileImage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://lh3.googleusercontent.com/a/AcJD_Fs0...._xzcWYzT=s96-c"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"authConnection"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"web3auth"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"userId"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"shahbaz@web3auth.io"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"groupedAuthConnectionId"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"web3auth-google-sapphire-mainnet"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"exp"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1747813890</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div><div class="alert-wrapper"><div class="cut-off-corners_XeOP top-left_okw5 bottom-right_dsEI medium_JsSc step-2_kLYr"><div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>NOTE</div><div class="admonitionContent_BuS1"><p>If the <strong>Return user data in identity token</strong> setting is disabled on the dashboard, personally identifiable information (PII) such as <strong>email</strong>, <strong>name</strong>, and <strong>profileImage</strong> will be omitted from the token.</p></div></div></div></div></div></div></details>

## Getting the ID Token[​](#getting-the-id-token "Direct link to Getting the ID Token")

To retrieve the ID token on the client-side, use the `getIdentityToken()` method. This is typically called after the user logs in.

- React SDK
- Vue SDK
- JavaScript SDK

```
import { useIdentityToken } from '@web3auth/modal/react'

function IdentityTokenButton() {
  const { getIdentityToken, loading, error, token } = useIdentityToken()

  return (
    <div>
      <button onClick={() => getIdentityToken()} disabled={loading}>
        {loading ? 'Authenticating...' : 'Get Identity Token'}
      </button>
      {token && <div>Token: {token}</div>}
      {error && <div>Error: {error.message}</div>}
    </div>
  )
}

```

```
<script setup lang="ts">
  import { useIdentityToken } from '@web3auth/modal/vue'

  const { getIdentityToken, loading, error, token } = useIdentityToken()
</script>

<template>
  <div>
    <button @click="getIdentityToken" :disabled="loading">
      {{ loading ? "Authenticating..." : "Get Identity Token" }}
    </button>
    <div v-if="token">Token: {{ token }}</div>
    <div v-if="error">Error: {{ error.message }}</div>
  </div>
</template>

```

```
try {
  const idToken = await web3auth.getIdentityToken()
  console.log(idToken)
} catch (error) {
  console.error('Error authenticating user:', error)
}

```

## Verifying the ID Token[​](#verifying-the-id-token "Direct link to Verifying the ID Token")

To validate an ID token server-side, use Web3Auth's JWKS endpoint or project-specific verification key. This process ensures the JWT was issued by Web3Auth and its contents have not been tampered with.

### Using the JWKS endpoint[​](#using-the-jwks-endpoint "Direct link to Using the JWKS endpoint")

Always validate `issuer` and `audience`

Every JWKS-based verification call **must** include both `issuer` and `audience` options:

- **`audience`** must equal your **Project Client ID**. Without it, a token issued by Web3Auth for any other project will pass signature verification — because all projects share the same signing infrastructure.
- **`issuer`** must match the Web3Auth endpoint that issued the token (`https://api-auth.web3auth.io` for social logins, `https://authjs.web3auth.io` for external wallets).

**Who is at risk without `audience` validation?**

| User identifier used by dapp          | Exploitable without aud? | Reason                                                                                           |
| ------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| userId / email / authConnectionId     | **Yes**                  | These claims are set by verifier configuration, which an attacker controls via their own project |
| wallets[].public_key (app-scoped key) | No                       | App keys are derived per-project; a different project produces different keys                    |
| wallets[].address (onchain address)   | No                       | Bound to the user's cryptographic keys, cannot be forged                                         |

If your backend matches users by `userId`, `email`, or `authConnectionId` — the common pattern for Web2-style user management — always validate `audience`.

- Social Login
- External Wallets
- Frontend Call

JWKS Endpoint: `https://api-auth.web3auth.io/jwks`

login/route.ts

```
import * as jose from 'jose'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  try {
    // Extract JWT token from Authorization header
    const authHeader = req.headers.get('authorization')
    const idToken = authHeader?.split(' ')[1]
    // Get public key from request body
    const { appPubKey } = await req.json()

    if (!idToken) {
      return NextResponse.json({ error: 'No token provided' }, { status: 401 })
    }

    if (!appPubKey) {
      return NextResponse.json({ error: 'No appPubKey provided' }, { status: 400 })
    }

    // Verify JWT using Web3Auth JWKS
    const jwks = jose.createRemoteJWKSet(new URL('https://api-auth.web3auth.io/jwks'))
    const { payload } = await jose.jwtVerify(idToken, jwks, {
      algorithms: ['ES256'],
      issuer: 'https://api-auth.web3auth.io',
      audience: process.env.WEB3AUTH_CLIENT_ID,
    })

    // Find matching wallet in JWT
    const wallets = (payload as any).wallets || []
    const normalizedAppKey = appPubKey.toLowerCase().replace(/^0x/, '')

    const isValid = wallets.some((wallet: any) => {
      if (wallet.type !== 'web3auth_app_key') return false

      const walletKey = wallet.public_key.toLowerCase()

      // Direct key comparison for ed25519 keys
      if (walletKey === normalizedAppKey) return true

      // Handle compressed secp256k1 keys
      if (
        wallet.curve === 'secp256k1' &&
        walletKey.length === 66 &&
        normalizedAppKey.length === 128
      ) {
        const compressedWithoutPrefix = walletKey.substring(2)
        return normalizedAppKey.startsWith(compressedWithoutPrefix)
      }

      return false
    })

    if (isValid) {
      return NextResponse.json({ name: 'Verification Successful' }, { status: 200 })
    } else {
      return NextResponse.json({ name: 'Verification Failed' }, { status: 400 })
    }
  } catch (error) {
    console.error('Social login verification error:', error)
    return NextResponse.json({ error: 'Verification error' }, { status: 500 })
  }
}

```

JWKS Endpoint: `https://authjs.web3auth.io/jwks`

login-external/route.ts

```
import * as jose from 'jose'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  try {
    // Extract JWT token from Authorization header
    const authHeader = req.headers.get('authorization')
    const idToken = authHeader?.split(' ')[1]
    // Get address from request body
    const { address } = await req.json()

    if (!idToken) {
      return NextResponse.json({ error: 'No token provided' }, { status: 401 })
    }

    if (!address) {
      return NextResponse.json({ error: 'No address provided' }, { status: 400 })
    }

    // Verify JWT using AuthJS JWKS
    const jwks = jose.createRemoteJWKSet(new URL('https://authjs.web3auth.io/jwks'))
    const { payload } = await jose.jwtVerify(idToken, jwks, {
      algorithms: ['ES256'],
      issuer: 'https://authjs.web3auth.io',
      audience: process.env.WEB3AUTH_CLIENT_ID,
    })

    // Find matching wallet in JWT
    const wallets = (payload as any).wallets || []
    const addressToCheck = Array.isArray(address) ? address[0] : address
    const normalizedAddress = addressToCheck.toLowerCase()

    const isValid = wallets.some((wallet: any) => {
      return (
        wallet.type === 'ethereum' &&
        wallet.address &&
        wallet.address.toLowerCase() === normalizedAddress
      )
    })

    if (isValid) {
      return NextResponse.json({ name: 'Verification Successful' }, { status: 200 })
    } else {
      return NextResponse.json({ name: 'Verification Failed' }, { status: 400 })
    }
  } catch (error) {
    console.error('External wallet verification error:', error)
    return NextResponse.json({ error: 'Verification error' }, { status: 500 })
  }
}

```

App.tsx

```
const { token, getIdentityToken, loading: idTokenLoading, error: idTokenError } = useIdentityToken()

const validateIdToken = async () => {
  const idToken = await getIdentityToken()

  let res
  if (connectorName === 'auth') {
    // Social login: send public key
    const pubKey = await web3Auth?.provider?.request({ method: 'public_key' })
    console.log('pubKey:', pubKey)
    res = await fetch('/api/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${idToken}`,
      },
      body: JSON.stringify({ appPubKey: pubKey }),
    })
  } else {
    // External wallet: send address
    const address = await web3Auth?.provider?.request({ method: 'eth_accounts' })
    res = await fetch('/api/login-external', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${idToken}`,
      },
      body: JSON.stringify({ address }),
    })
  }

  // Handle response
  if (res.status === 200) {
    toast.success('JWT Verification Successful')
    uiConsole(`Logged in Successfully!`, userInfo)
  } else {
    toast.error('JWT Verification Failed')
    await disconnect()
  }

  return res.status
}

```

### Using the verification key[​](#using-the-verification-key "Direct link to Using the verification key")

To manually verify the token, use your **Verification Key** available on the **Project Settings** page in the dashboard.

- jose
- jsonwebtoken

```
npm install jose

```

Example (JWT Verification using jose)

```
const verificationKey = await jose.importSPKI('insert-your-web3auth-verification-key', 'ES256')

const idToken = 'insert-the-users-id-token'

try {
  const payload = await jose.jwtVerify(idToken, verificationKey, {
    issuer: 'https://api-auth.web3auth.io',
    audience: 'your-project-client-id',
  })
  console.log(payload)
} catch (error) {
  console.error(error)
}

```

```
npm install jsonwebtoken

```

Example (JWT Verification using jsonwebtoken)

```
const verificationKey = 'insert-your-web3auth-verification-key'.replace(/\\n/g, '\n')

const idToken = 'insert-the-users-id-token'

try {
  const decoded = jwt.verify(idToken, verificationKey, {
    issuer: 'https://api-auth.web3auth.io',
    audience: 'your-project-client-id',
  })
  console.log(decoded)
} catch (error) {
  console.error(error)
}

```

note

The replace operation above ensures that any instances of '\n' in the stringified public key are replaced with actual newlines, per the PEM-encoded format.

info

If the token is valid, the payload will contain identity claims (such as, userId). If invalid, an error is thrown.

## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting")

- Validate that `iss` equals `https://api-auth.web3auth.io` (social logins) or `https://authjs.web3auth.io` (external wallets). A mismatch means the token was not issued by the expected Web3Auth service.
- Validate that `aud` equals your **Project Client ID**. A mismatch means the token was issued for a different project and must be rejected.
- Validate that `exp` is in the future. Expired tokens must be rejected.
- Validate that `iat` is in the past. A future `iat` indicates a malformed or tampered token.
