Verify a user owns a wallet by asking them to sign a message, then validate the signature on your backend.Documentation Index
Fetch the complete documentation index at: https://docs.phantom.com/llms.txt
Use this file to discover all available pages before exploring further.
- React
- Browser SDK
- React Native
import { useSolana, useAccounts } from "@phantom/react-sdk";
function SignInWithSolana() {
const { solana } = useSolana();
const addresses = useAccounts();
const signIn = async () => {
const address = addresses?.[0]?.address;
if (!solana || !address) return;
// Create a sign-in message
const message = `Sign in to MyApp
Address: ${address}
Timestamp: ${new Date().toISOString()}
Nonce: ${crypto.randomUUID()}`;
// Request signature
const { signature } = await solana.signMessage(message);
// Send to your backend for verification
const response = await fetch("/api/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ address, message, signature }),
});
const { token } = await response.json();
console.log("Authenticated! Token:", token);
};
return <button onClick={signIn}>Sign in with Solana</button>;
}
import { BrowserSDK, AddressType } from "@phantom/browser-sdk";
const sdk = new BrowserSDK({
providers: ["google", "apple", "injected"],
appId: "your-app-id",
addressTypes: [AddressType.solana],
});
async function signIn() {
const address = await sdk.solana.getPublicKey();
const message = `Sign in to MyApp
Address: ${address}
Timestamp: ${new Date().toISOString()}
Nonce: ${crypto.randomUUID()}`;
const { signature } = await sdk.solana.signMessage(message);
// Send to backend for verification
const response = await fetch("/api/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ address, message, signature }),
});
const { token } = await response.json();
return token;
}
import { useSolana, useAccounts } from "@phantom/react-native-sdk";
import { View, Button, Alert, StyleSheet } from "react-native";
function SignInWithSolana() {
const { solana } = useSolana();
const addresses = useAccounts();
const signIn = async () => {
try {
const address = addresses?.[0]?.address;
if (!solana || !address) {
Alert.alert("Error", "Please connect your wallet first");
return;
}
const message = `Sign in to MyApp
Address: ${address}
Timestamp: ${new Date().toISOString()}
Nonce: ${crypto.randomUUID()}`;
const { signature } = await solana.signMessage(message);
const response = await fetch("/api/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ address, message, signature }),
});
const { token } = await response.json();
Alert.alert("Success", "Authenticated!");
console.log("Token:", token);
} catch (error) {
Alert.alert("Error", error.message);
}
};
return (
<View style={styles.container}>
<Button title="Sign in with Solana" onPress={signIn} />
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
});
Verify on the backend
// api/auth/verify.ts
import { PublicKey } from "@solana/web3.js";
import nacl from "tweetnacl";
import bs58 from "bs58";
import jwt from "jsonwebtoken";
export async function POST(request: Request) {
const { address, message, signature } = await request.json();
// Verify the signature
const publicKey = new PublicKey(address);
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = bs58.decode(signature);
const isValid = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKey.toBytes()
);
if (!isValid) {
return Response.json({ error: "Invalid signature" }, { status: 401 });
}
// Create a session token
const token = jwt.sign({ address }, process.env.JWT_SECRET, { expiresIn: "7d" });
return Response.json({ token });
}