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.
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
**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
You must configure which domain you're using with Solana Auth in two places:
- The AUTH_DOMAIN env variable on the server (in your
.env
file)
#/.env
AUTH_DOMAIN=example.xyz
- The authDomain property from SolanaAuthProvider
<SolanaAuthProvider
authDomain="example.xyz"
{...}
>
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;
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
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>;
}
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() });
If you encounter any issues, please join our Discord!
- This package is provided as-is. We have done our best to reduce security vulnerabilities; however, we cannot guarantee that we have complete coverage.
- This library does not yet support Ledger wallets. This is due to an unresolved issue between Ledger and Solana.
- If you are adding this to an existing Next.js project, you will need to install
next-transpile-modules
and include the following in yournext.config.js
const withTM = require("next-transpile-modules")([
"@crossmint/solana-auth-react-ui",
]);
module.exports = withTM({
reactStrictMode: true,
});