DEV Community

Cover image for Sign-In with Solana
Cibrax
Cibrax

Posted on

Sign-In with Solana

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,
   },
});
Enter fullscreen mode Exit fullscreen mode

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}/`
})
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
morgansson profile image
morgansson • Edited

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.

Collapse
 
cibrax profile image
Cibrax

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.

Collapse
 
t4t5 profile image
Tristan

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!

Collapse
 
math1987 profile image
Math17

Hey Cibrax.
Do you know how to sign a message without wallet, for backend tests ?
I'm trying without success xD.

Collapse
 
cibrax profile image
Cibrax

I haven't tried but it should not be different. Are you using javascript on the backend? I will take a look. Thanks

Collapse
 
math1987 profile image
Math17

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)

Collapse
 
math1987 profile image
Math17

Thanks a lot Cibrax !