Transactions change the blockchain state and cost gas. Sometimes, you just want to prove who you are.
Message Signing (standardized in NEP-413) allows a user to sign a piece of data with their private key off-chain. This is free, instant, and is the standard way to implement “Log in with NEAR”.
How it Works
- Client: Generates a random “nonce” and asks the user to sign a specific message.
- Wallet: Shows the message to the user. If approved, it returns a cryptographic signature.
- Backend: Verifies the signature against the user’s public key and checks the nonce to prevent replay attacks.
1. The Client (Frontend)
Use near.signMessage to request a signature.
You must generate a random nonce. This ensures that a captured signature cannot be re-used by an attacker later.
The signature field is base64 encoded per the NEP-413 specification. Send it unchanged to your backend.
2. The Server (Backend)
Automatic Expiration: Nonces created with generateNonce() embed a timestamp in their first 8 bytes. This is a near-kit convention (NEP-413 itself treats the nonce as arbitrary bytes) that lets verifyNep413Signature automatically reject signatures older than 5 minutes, limiting the replay attack window.
This verifies the cryptographic signature and confirms the public key belongs to the claimed account as a full access key.
Customize expiration window if needed:
You can also check key existence directly:
Cryptographic verification alone is not enough! If you do not check if the nonce has been used before, an attacker who intercepts the signed message can “replay” it to your server to log in as the user again.Always store used nonces in your database (with an expiration time) and reject duplicates.
Custom Nonce Schemes
NEP-413 defines the nonce as an opaque 32-byte value — the timestamp-in-the-first-8-bytes layout is just near-kit’s convention. Some apps define their own nonce structure instead (for example, intents.near uses a versioned, salted, expirable nonce).
By default, verifyNep413Signature interprets the first 8 bytes of the nonce as a timestamp, so it will reject valid signatures that use a different scheme. Pass nonceValidation: "none" to treat the nonce as opaque bytes per the spec:
Type Definition
The SignedMessage object returned by the client and sent to the server looks like this:
near.signMessage returns a base64-encoded signature as specified in NEP-413. verifyNep413Signature also accepts legacy base58 signatures (with or without key type prefix) for backward compatibility.