Solana has become lately one of the hottest programmable Blockchains after Ethereum. Since the adoption of Solana is growing, and also the number of people using one of their wallets, it might be convenient to start looking into how to support one-click authentication for web sites.
This post will show how to enable that scenario with Phantom.
One-Click authentication with signatures
Either Ethereum or Solana supports the idea of signing text messages with the user's private key available on a wallet. Since only the user owns that private key, and it is the only one who can generate an equivalent signature, this is proof enough to use it as an authentication mechanism. A This scenario uses a combination of Signature + Public Key/Address. As an analogy to a traditional authentication method like username and password, the Public Key/Address would be equivalent to the username, and the signature to a password.
Signing a text message with Phantom
The following code shows how to use Phantom to sign a message. The user will be prompted to authorize this operation.
const message = `Sign this message for authenticating with your wallet. Nonce: ${nonce}`;
const encodedMessage = new TextEncoder().encode(message);
const signedMessage = await solana.request({
method: "signMessage",
params: {
message: encodedMessage,
},
});
A nonce was generated server-side and injected in the text message to avoid reply attacks, in which the user signature is intercepted and reused for authentication later on.
This sample uses NextAuth for integrating authentication in a Next.js application. The signature and public key are passed to the SignIn function provided by NextAuth.
signIn('credentials',
{
publicKey: signedMessage.publicKey,
signature: signedMessage.signature,
callbackUrl: `${window.location.origin}/`
})
Verifying the signature on the server side.
The server receives the signature and public key and verifies whether the former is valid. The user is authenticated once this validation passes successfully.
const nonce = req.cookies["auth-nonce"];
const message = `Sign this message for authenticating with your wallet. Nonce: ${nonce}`;
const messageBytes = new TextEncoder().encode(message);
const publicKeyBytes = bs58.decode(credentials.publicKey);
const signatureBytes = bs58.decode(credentials.signature);
const result = nacl.sign.detached.verify(messageBytes, signatureBytes, publicKeyBytes);
if (!result) {
console.log(`authentication failed`);
throw new Error("user can not be authenticated");
}
const user = { name: credentials.publicKey }
return user;
This code retrieves the generated nonce from a session cookie, recreates the text message, and validates the user's signature with the public key passed by the client side.
Once the signature is validated, the public key is set as the username for the user.
The complete sample is available to download from my Github repository solana-login
Top comments (7)
Hello, great article! Thanks for sharing this valuable example.
I have a doubt about how can you avoid replay attacks with just a nonce?
Let's set an example: Another website makes you sign an exact message as the one you are using to login to the original website, utilizing and saving a random fake nonce. Then the attacker (the another website owner) could utilize this signature plus the saved fake nonce, and be able to login to your account.
In the presented code, the only server-side check is that the cookie "auth-nonce" sent by the user it's the same as the one in the message, which absolutely will be in the scenario I'm mentioning.
Unless I'm missing something here, it doesn't sound like a secure solution.
Thanks in advance, and again, great article. Looking forward to implement this on our dApp.
The nonce is generated server side and set in a cookie valid only for that website. The browser won't pass that cookie for any other random website.
Hi there! I'm still not sure I understand how this is secure.
If an attacker has managed to get you to sign the same message with a random nonce, what prevents them from going to your site, manually setting their
auth-nonce
cookie to that random value and then passing the acquired user's signature to your API in order to log in as them?Thanks!
Hey Cibrax.
Do you know how to sign a message without wallet, for backend tests ?
I'm trying without success xD.
I haven't tried but it should not be different. Are you using javascript on the backend? I will take a look. Thanks
Hi, thanks!
Yes i'm using nodejs. Well i'm pretty newbie on solana so my problem is probably about understanding what instructions to set into the creation of a message.
But...i'm working on ! (so i skipped the test for instance and continue learning)
Thanks a lot Cibrax !