README.md 8.4 KB

Sign-In With Solana

Introduction

Sign In With Solana (SIWS) is a new feature that lets dapps authenticate their users and prove ownership of their addresses. The feature aims to replace the traditionally clunky connect + signMessage flow with a one-click signIn method.

Phantom is pioneering support for SIWS alongside the Solana Wallet Standard. The technical specification for SIWS is modeled after EIP-4361 (Sign In With Ethereum) but extends beyond it’s capabilities. SIWS shifts the responsibility of message construction from dapps to the wallet, resulting in consistent, user-friendly interfaces and enhanced end-user security.

drawing drawing

With SIWS, wallets can scrutinize message data to ensure its legitimacy or raise red flags for suspicious activity. Domain binding is a key feature of SIWS, enabling wallets to alert users if a website is impersonating another entity

drawing drawing

Specification

  • SolanaSignInInput:

    /**
    * Optional EIP-4361 Domain.
    * If not provided, the wallet must determine the Domain to include in the message.
    */
    domain?: string;
        
    /**
    * Optional EIP-4361 Address.
    * If not provided, the wallet must determine the Address to include in the message.
    */
    address?: string;
        
    /**
    * Optional EIP-4361 Statement.
    * If not provided, the wallet must not include Statement in the message.
    */
    statement?: string;
        
    /**
    * Optional EIP-4361 URI.
    * If not provided, the wallet must not include URI in the message.
    */
    uri?: string;
        
    /**
    * Optional EIP-4361 Version.
    * If not provided, the wallet must not include Version in the message.
    */
    version?: string;
        
    /**
    * Optional EIP-4361 Chain ID.
    * If not provided, the wallet must not include Chain ID in the message.
    */
    chainId?: string;
        
    /**
    * Optional EIP-4361 Nonce.
    * If not provided, the wallet must not include Nonce in the message.
    */
    nonce?: string;
        
    /**
    * Optional EIP-4361 Issued At.
    * If not provided, the wallet must not include Issued At in the message.
    */
    issuedAt?: string;
        
    /**
    * Optional EIP-4361 Expiration Time.
    * If not provided, the wallet must not include Expiration Time in the message.
    */
    expirationTime?: string;
        
    /**
    * Optional EIP-4361 Not Before.
    * If not provided, the wallet must not include Not Before in the message.
    */
    notBefore?: string;
        
    /**
    * Optional EIP-4361 Request ID.
    * If not provided, the wallet must not include Request ID in the message.
    */
    requestId?: string;
        
    /**
    * Optional EIP-4361 Resources.
    * If not provided, the wallet must not include Resources in the message.
    */
    resources?: string[];
    
  • SolanaSignInOutput:

    /**
    * Account that was signed in.
    * The address of the account may be different from the provided input Address.
    */
    account: WalletAccount;
        
    /**
     * Message bytes that were signed.
     * The wallet may prefix or otherwise modify the message before signing it.
     */
    signedMessage: Uint8Array;
        
    /**
     * Message signature produced.
     * If the signature type is provided, the signature must be Ed25519.
     */
    signature: Uint8Array;
        
    /**
     * Optional type of the message signature produced.
     * If not provided, the signature must be Ed25519.
     */
    signatureType?: 'ed25519';
    

Integrating SIWS

SIWS comes with first-class support in both the Solana Wallet Standard and Solana Wallet Adapter libraries. If your dapp makes use of the Solana Wallet Adapter, migration is easy:

  1. Download the attached Phantom build
  2. Update the necessary dependencies
  3. Add this snippet in your ContextProvider :

    import { type SolanaSignInInput } from '@solana/wallet-standard-features';
    import { verifySignIn } from '@solana/wallet-standard-util';
        
    const autoSignIn = useCallback(async (adapter: Adapter) => {
         if (!('signIn' in adapter)) return true;
        
             // For demo purposes only: The signInInput should
         // be generated server-side.
         const input: SolanaSignInInput = {
              statement: "Welcome to Drip!"
         };
         const output = await adapter.signIn(input);
                    
             // For demo purposes only: The sign-in verification
         // should happen server-side.
         if (!verifySignIn(input, output)) throw new Error('Sign In verification failed!');
        
         return false;
    }, []);
    

    This callback function determines whether a user should be auto-connected (returns true) or prompted to sign-in (returns false). It does this by:

    • Checking if the user’s wallet supports the signIn feature.
      • If signIn is not available, this callback returns true
    • If signIn is available, checking if:
      • The SolanaSignInInput object is prepared
      • The input is passed into the signIn method
      • The output is verified using the verifySignIn method
      • If all of the above, the callback returns false and the user is prompted to sign-in
  4. Pass autoSignIn to WalletProvider like so:

    <WalletProvider
     wallets={wallets}
     onError={onError}
     autoConnect={autoSignIn}
    >
    

Best Practices

  • In a production application, both SolanaSignInInput generation and SolanaSignInOutput verification should happen server-side. Client-side verification should not be relied upon due to its inherent security flaws. Developers can make use of the asynchronous nature of the autoSignIn callback to make a request to the server to obtain SIWS params, and make another request to the server to perform verification of the SIWS input and output.
  • When generating SolanaSignInInput, developers should include as many fields as possible. Each field adds an extra layer of security to protect users against various forms of attacks. Examples of these fields include nonce, chainId, issuedAt, expirationTime, and requestId.

Reference API

  • issuedAt` — the time at which wahateve, — Phantom currently accepts _+10 min but subject to change
  • In addition to using nonce to avoid replay attacks, dapps can also choose to include a unique signature in the requestId . Once the wallet returns the signed message, dapps can then verify this signature against the state to add an additional, strong layer of security.

Dependencies

These dependencies need to be added to your package.json:

"@solana/wallet-adapter-base": "0.9.23",
"@solana/wallet-adapter-react": "0.15.34",
"@solana/wallet-standard-features": "1.1.0",
"@solana/wallet-standard-util": "1.1.0",

Then, add the following to your package.json :

"resolutions": {
  "@solana/wallet-adapter-react": "0.15.34",
  "@solana/wallet-adapter-base": "0.9.23"
  
},
"overrides": {
  "@solana/wallet-adapter-react": "0.15.34",
  "@solana/wallet-adapter-base": "0.9.23"
}

These configs will be important while testing as some packages like @solana/wallet-adapter-material-ui and @solana/wallet-adapter-react-ui use older versions of the react and base packages and may cause conflicts.

Full Feature Demo

https://www.loom.com/share/228d2a4820fb44f69fb10c4fb5f2b55a?sid=21478434-bd5f-438d-a526-0b2439b2cde8

Reference Implementation

Repository:

https://github.com/0xproflupin/sign-in-with-solana

App: https://siws.vercel.app/