Skip to content

Sign-in with Solana wallet, with server+client side authentication and persistent sessions

License

Notifications You must be signed in to change notification settings

Crossmint/solana-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

99 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@crossmint/solana-auth

CrossMint's solana-auth provides developers with the tools to configure their applications to allow users to authenticate with their Solana wallets.

This package currently provides support for Firebase out of the box but allows developers to configure it to work with any backend/database.

πŸš€ Quick Setup (Next.js Example)

Install

npx create-next-app crossmint-solana-auth --example "https://github.com/crossmint/solana-auth/tree/main/packages/examples/nextjs-starter"

cd crossmint-solana-auth && yarn install
yarn dev

πŸ›  Setup (React + Firebase)

Install

**If you are adding to an existing Next.js project, please see Caveat #3

yarn add @crossmint/solana-auth-base@beta  \
         @crossmint/solana-auth-react-ui@beta

Configure domain

You must configure which domain you're using with Solana Auth in two places:

  1. The AUTH_DOMAIN env variable on the server (in your .env file)
#/.env
AUTH_DOMAIN=example.xyz
  1. The authDomain property from SolanaAuthProvider
 <SolanaAuthProvider
      authDomain="example.xyz"
      {...}
    >

Define auth functions

Define what happens when a user is signed in and signed out by implementing signIn and signOut.

You can define this in a util file or in /pages/_app.tsx where they are used by the <SolanaAuthProvider/>

Here we use those events to handle a session using firebase-auth by using custom auth.

// utils/firebaseClient.ts
export async function signIn(data: Record<string, string>) {
  const userCredential = await signInWithCustomToken(auth, data.token);
  return userCredential;
}

export const signOut = () => {
  return auth.signOut();
};

Wrap your app in SolanaAuthProvider. Make sure to pass it a domain, your auth functions (defined above), and your serverless endpoints (defined below).

// _app.tsx
import { AppProps } from "next/app";
import { FC } from "react";
import { signIn, signOut } from "../utils/firebaseClient"; // DEFINED ABOVE

import { SolanaAuthProvider } from "@crossmint/solana-auth-react-ui";

require("../styles/globals.css");

const App: FC<AppProps> = ({ Component, pageProps }) => {
  return (
    <SolanaAuthProvider
      authDomain="example.xyz"
      onAuthCallback={signIn}
      signOut={signOut}
      {/** DEFINED IN THE NEXT STEP */}
      requestUrl="/api/solana-auth/getauthchallenge"
      callbackUrl="/api/solana-auth/completeauthchallenge"
    >
      <Component {...pageProps} />
    </SolanaAuthProvider>
  );
};

export default App;

Use the SolanaAuthButtonFirebase component to render the signin button in your app.

import { SolanaAuthButtonFirebase } from "@crossmint/solana-auth-react-ui";
import { auth } from "../utils/firebaseClient";
const Index = () => {
  return (
    <div>
      <head>
        <title>Sign in with Solana</title>
        <meta name="description" content="Sign in with Solana" />
        <link rel="icon" href="/favicon.ico" />
      </head>

      <main>
        <SolanaAuthButtonFirebase auth={auth} />
      </main>
    </div>
  );
};

export default Index;

Use the serverless functions.

import { FirebaseAdapter, SolanaAuth } from "@crossmint/solana-auth-base";
import firebaseApp from "../../../utils/firebase";

export default SolanaAuth({ adapter: FirebaseAdapter(firebaseApp) });

This object exposes getAuthChallenge and completeAuthChallenge. These functions take a request and response like vercel or firebase functions and return Promise<void>.

These functions must be used on the api routes defined in above in the SolanaAuthProvider

Custom Configuration

Defining a database adapter

Database adapters are needed in order to store transient log-in attempt information. We provide a built-in adapter for Firebase Firestore, or you can implement your own.

If you are using Firebase, we have built a database adapter for you, accesible with @crossmint/solana-auth-base

Database adapters must fit the Adapter interface

type SignInAttempt = {
  nonce: string;
  ttl: number;
  pubkey: string;
};

interface Adapter {
  saveSigninAttempt(attempt: SignInAttempt): Promise<void>;
  getNonce(pubkey: string): Promise<any>;
  generateToken(pubkey: string): Promise<string>;
  getTLL(pubkey: string): Promise<number>;
}

Custom Adapter Template

const CustomAdapter = (): Adapter => ({
  saveSigninAttempt(attempt: SignInAttempt): Promise<void> {
    // TODO: save attempt object into persistent storage. Use the as primary key
    // and replace any existing entry with the same key
  },
  getNonce(pubkey: string): Promise<any> {
    // TODO: retrieve the nonce of the most recent sign-in attempt for a public key and return the nonce or undefined
  },
  generateToken(pubkey: string): Promise<string> {
    // TODO: create an authentication token for the user and return the token
  },
  getTLL(pubkey: string): Promise<number> {
    // TODO: get the Time To Live from a SignInAttempt from the database
  },
});

A custom database adapter can be passed to the SolanaAuth constructor like so:

SolanaAuth({ adapter: CustomAdapter() });

Support

If you encounter any issues, please join our Discord!

Caveats

  1. This package is provided as-is. We have done our best to reduce security vulnerabilities; however, we cannot guarantee that we have complete coverage.
  2. This library does not yet support Ledger wallets. This is due to an unresolved issue between Ledger and Solana.
  3. If you are adding this to an existing Next.js project, you will need to install next-transpile-modules and include the following in your next.config.js
const withTM = require("next-transpile-modules")([
  "@crossmint/solana-auth-react-ui",
]);
module.exports = withTM({
  reactStrictMode: true,
});