Use Your Own Auth System
Authenticate users with your existing identity provider via custom JWT
Use Your Own Auth System
Bring your existing authentication system (Auth0, Firebase, Cognito, or any OIDC provider) and use its JWTs to create MPC wallets. Users authenticate through your identity provider as usual, and the NERO SDK verifies the JWT to provision a wallet tied to that identity.
Prerequisites
@nerochain/mpc-sdkinstalled- An OIDC-compliant identity provider that issues JWTs (Auth0, Firebase Auth, AWS Cognito, Okta, etc.)
- Your provider's issuer URL and JWKS endpoint
How It Works
- Register a verifier -- tell the NERO backend how to validate JWTs from your provider
- User authenticates -- through your existing login flow (your UI, your provider)
- Pass the JWT -- call
loginWithCustomJwt()with the user's ID token and your verifier ID - Wallet is created -- the SDK verifies the JWT signature against your provider's JWKS, authenticates the user, and generates an MPC wallet tied to that identity
Step 1 -- Register a JWT Verifier
Before users can log in, register your identity provider as a verifier. Do this once per provider, either through the NERO dashboard or programmatically via useNeroAdmin.
Option A: Via the Dashboard
- Go to your project settings in the NERO dashboard
- Navigate to "JWT Verifiers"
- Click "Add Verifier"
- Fill in your provider details:
- Name: A label for this verifier (e.g., "Production Auth0")
- Type:
oidcorfirebase - Issuer URL: Your provider's issuer (e.g.,
https://my-tenant.auth0.com/) - JWKS URL: The public key endpoint (e.g.,
https://my-tenant.auth0.com/.well-known/jwks.json) - Audience: The audience claim expected in the JWT (your API identifier)
- Save and copy the Verifier ID
Option B: Programmatically
Use the useNeroAdmin hook to create a verifier from your application code. This requires an authenticated admin session.
import { useNeroAdmin } from "@nerochain/mpc-sdk/react";
function VerifierSetup() {
const { createVerifier, listVerifiers, isLoading, error } = useNeroAdmin();
const [verifierId, setVerifierId] = useState<string | null>(null);
const handleCreate = async () => {
const result = await createVerifier({
name: "My Auth0",
type: "oidc",
issuer: "https://my-tenant.auth0.com/",
jwksUrl: "https://my-tenant.auth0.com/.well-known/jwks.json",
audience: "my-api",
});
setVerifierId(result.id);
};
const handleList = async () => {
const verifiers = await listVerifiers();
console.log("Registered verifiers:", verifiers);
};
return (
<div>
<button onClick={handleCreate} disabled={isLoading}>
Create Verifier
</button>
<button onClick={handleList} disabled={isLoading}>
List Verifiers
</button>
{verifierId && <p>Verifier ID: <code>{verifierId}</code></p>}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}The createVerifier config accepts:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for this verifier |
type | "oidc" | "firebase" | Yes | Provider type |
issuer | string | Yes | The iss claim in the JWT (must match exactly) |
jwksUrl | string | No | URL to fetch public keys for signature verification. Defaults to {issuer}/.well-known/jwks.json |
audience | string | No | Expected aud claim in the JWT |
Step 2 -- Authenticate Users with Your Provider
Your existing auth flow stays unchanged. Users log in through your UI, and you receive a JWT (ID token) from your provider. The specific implementation depends on your provider:
// Auth0 example
const { getIdTokenClaims } = useAuth0();
const claims = await getIdTokenClaims();
const idToken = claims.__raw;
// Firebase example
const idToken = await firebase.auth().currentUser.getIdToken();
// Generic OIDC
const idToken = sessionStorage.getItem("id_token");Step 3 -- Log In with the JWT
Pass the JWT to loginWithCustomJwt() along with the verifier ID from step 1. The SDK validates the token against your provider's JWKS and creates (or restores) an MPC wallet for that user identity.
import { useNeroConnect } from "@nerochain/mpc-sdk/react";
function CustomLogin() {
const { loginWithCustomJwt, isConnecting, error } = useNeroConnect();
const handleLogin = async (idToken: string) => {
await loginWithCustomJwt({
idToken,
verifierId: "your-verifier-id",
});
// User is now authenticated with an MPC wallet
};
return (
<div>
<button onClick={() => handleLogin(getTokenFromYourAuth())} disabled={isConnecting}>
{isConnecting ? "Creating wallet..." : "Connect Wallet"}
</button>
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}Complete Working Component
This example shows the full flow: managing verifiers (admin side) and logging in with a JWT (user side).
import { useState } from "react";
import { useNeroAdmin, useNeroConnect, useNeroWallet } from "@nerochain/mpc-sdk/react";
export default function CustomAuthGuide() {
const { createVerifier, listVerifiers, isLoading: adminLoading, error: adminError } = useNeroAdmin();
const { loginWithCustomJwt, isConnecting, error: connectError } = useNeroConnect();
const { wallet } = useNeroWallet();
const [tab, setTab] = useState<"setup" | "login">("setup");
const [verifierId, setVerifierId] = useState("");
const [jwt, setJwt] = useState("");
const [verifiers, setVerifiers] = useState<unknown[]>([]);
// Verifier config fields
const [name, setName] = useState("My Auth0");
const [type, setType] = useState<"oidc" | "firebase">("oidc");
const [issuer, setIssuer] = useState("");
const [jwksUrl, setJwksUrl] = useState("");
const [audience, setAudience] = useState("");
const handleCreateVerifier = async () => {
try {
const config: Parameters<typeof createVerifier>[0] = { name, type, issuer };
if (jwksUrl) config.jwksUrl = jwksUrl;
if (audience) config.audience = audience;
const result = await createVerifier(config);
setVerifierId(result.id);
} catch {
// error state handled by hook
}
};
const handleListVerifiers = async () => {
try {
const result = await listVerifiers();
setVerifiers(result as unknown[]);
} catch {
// error state handled by hook
}
};
const handleLogin = async () => {
if (!jwt || !verifierId) return;
try {
await loginWithCustomJwt({ idToken: jwt, verifierId });
} catch {
// error state handled by hook
}
};
const error = adminError ?? connectError;
return (
<div style={{ maxWidth: 520, margin: "0 auto" }}>
<h2>Custom JWT Authentication</h2>
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
<button
onClick={() => setTab("setup")}
style={{ fontWeight: tab === "setup" ? "bold" : "normal" }}
>
1. Setup Verifier
</button>
<button
onClick={() => setTab("login")}
style={{ fontWeight: tab === "login" ? "bold" : "normal" }}
>
2. Login with JWT
</button>
</div>
{error && <p style={{ color: "red" }}>{error.message}</p>}
{tab === "setup" && (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Verifier name" />
<select value={type} onChange={(e) => setType(e.target.value as "oidc")}>
<option value="oidc">OIDC</option>
<option value="firebase">Firebase</option>
</select>
<input value={issuer} onChange={(e) => setIssuer(e.target.value)} placeholder="Issuer URL (required)" />
<input value={jwksUrl} onChange={(e) => setJwksUrl(e.target.value)} placeholder="JWKS URL (optional)" />
<input value={audience} onChange={(e) => setAudience(e.target.value)} placeholder="Audience (optional)" />
<div style={{ display: "flex", gap: 8 }}>
<button onClick={handleCreateVerifier} disabled={adminLoading || !issuer}>
Create Verifier
</button>
<button onClick={handleListVerifiers} disabled={adminLoading}>
List Verifiers
</button>
</div>
{verifierId && <p>Verifier ID: <code>{verifierId}</code></p>}
{verifiers.length > 0 && <pre>{JSON.stringify(verifiers, null, 2)}</pre>}
</div>
)}
{tab === "login" && (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<input
value={verifierId}
onChange={(e) => setVerifierId(e.target.value)}
placeholder="Verifier ID"
/>
<textarea
value={jwt}
onChange={(e) => setJwt(e.target.value)}
placeholder="Paste your JWT (ID token) here"
rows={4}
style={{ fontFamily: "monospace", fontSize: 12 }}
/>
<button onClick={handleLogin} disabled={isConnecting || !jwt || !verifierId}>
{isConnecting ? "Authenticating..." : "Login with JWT"}
</button>
{wallet && (
<div style={{ padding: 12, background: "#f0fdf4", borderRadius: 8, marginTop: 8 }}>
<p>Wallet created: <code>{wallet.eoaAddress}</code></p>
</div>
)}
</div>
)}
</div>
);
}What the User Sees
- Your login page -- the user logs in through your existing auth UI (unchanged)
- Wallet provisioning -- a brief "Creating wallet..." state while the SDK verifies the JWT and runs DKG
- Connected -- the user has an MPC wallet tied to their existing identity, with no separate "crypto login" step
Provider-Specific Configuration
Auth0
await createVerifier({
name: "Auth0 Production",
type: "oidc",
issuer: "https://YOUR_DOMAIN.auth0.com/",
audience: "YOUR_API_IDENTIFIER",
});Firebase Auth
await createVerifier({
name: "Firebase Production",
type: "firebase",
issuer: "https://securetoken.google.com/YOUR_PROJECT_ID",
});AWS Cognito
await createVerifier({
name: "Cognito Production",
type: "oidc",
issuer: "https://cognito-idp.REGION.amazonaws.com/USER_POOL_ID",
jwksUrl: "https://cognito-idp.REGION.amazonaws.com/USER_POOL_ID/.well-known/jwks.json",
});Error Handling
try {
await loginWithCustomJwt({ idToken, verifierId });
} catch (err) {
if (err.message.includes("invalid_token")) {
// JWT signature verification failed
} else if (err.message.includes("expired")) {
// JWT has expired -- refresh it from your provider
} else if (err.message.includes("verifier_not_found")) {
// Verifier ID doesn't exist -- check the dashboard
}
}| Error | Cause | Fix |
|---|---|---|
| Invalid token | JWT signature doesn't match the JWKS public keys | Ensure the issuer URL and JWKS endpoint are correct |
| Token expired | The JWT exp claim is in the past | Get a fresh token from your identity provider |
| Verifier not found | The verifierId doesn't match a registered verifier | Check the verifier ID in the dashboard or call listVerifiers() |
| Audience mismatch | JWT aud claim doesn't match the verifier's audience config | Update the audience in the verifier config or in your provider settings |
| Issuer mismatch | JWT iss claim doesn't match the verifier's issuer URL | Ensure the issuer URL includes the trailing slash if your provider uses one |