https://github.com/random-bits-studio/use-siwe
The easiest way to add Sign-in with Ethereum to your app.
https://github.com/random-bits-studio/use-siwe
auth authentication ethereum express expressjs ironsession next nextjs react session siwe wagmi web3
Last synced: 8 months ago
JSON representation
The easiest way to add Sign-in with Ethereum to your app.
- Host: GitHub
- URL: https://github.com/random-bits-studio/use-siwe
- Owner: random-bits-studio
- License: mit
- Created: 2022-09-19T15:05:04.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2023-10-28T07:23:00.000Z (about 2 years ago)
- Last Synced: 2025-02-17T17:17:18.769Z (9 months ago)
- Topics: auth, authentication, ethereum, express, expressjs, ironsession, next, nextjs, react, session, siwe, wagmi, web3
- Language: TypeScript
- Homepage:
- Size: 365 KB
- Stars: 25
- Watchers: 2
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
- awesome-nextjs - UseSIWE - React hooks and Next.js API routes that make it super easy to add Sign-In with Ethereum to your app. (Extensions)
- fucking-awesome-nextjs - UseSIWE - React hooks and Next.js API routes that make it super easy to add Sign-In with Ethereum to your app. (Extensions)
README
# UseSIWE
UseSIWE is a library that provides react hooks and API endpoints that make it
dead simple to add Sign-In with Ethereum functionality to your react
application.
### 🌈 Works with RainbowKit
The easiest way to use this library is with RainbowKit!
Check out the RainbowKit authentication adapter for UseSiwe here:
https://github.com/random-bits-studio/rainbowkit-use-siwe-auth
# Table of Contents
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Configure settings for iron-session](#configure-settings-for-iron-session)
- [Setting up the API routes](#setting-up-the-api-routes)
- [Next.js](#nextjs)
- [Express.js](#expressjs)
- [Wrapping your application with SiweProvider](#wrapping-your-application-with-siweprovider)
- [Using the hooks](#using-the-hooks)
- [Checking if a user is authenticated](#checking-if-a-user-is-authenticated)
- [Signing In](#signing-in)
- [Signing Out](#signing-out)
- [API](#api)
- [Types](#types)
- [UseSiweOptions](#usesiweoptions)
- [Components](#components)
- [SiweProvider](#siweprovider)
- [Hooks](#hooks)
- [useSession](#usesession)
- [useSignIn](#usesignin)
- [useSignOut](#usesignout)
- [useOptions](#useoptions)
- [Routes](#routes)
- [Next.js: SiweApi](#nextjs-siweapi)
- [Express.js: SiweApi](#expressjs-siweapi)
- [Functions](#functions)
- [getSession](#getsession)
- [createMessage](#createmessage)
- [getMessageBody](#getmessagebody)
- [verify](#verify)
- [signOut](#signout)
# Installation
To install UseSIWE and it's dependencies run the following command:
```
npm install @randombits/use-siwe wagmi ethers iron-session
```
# Getting Started
## Configure settings for `iron-session`
Copy and paste the following code into a new file in your project:
```ts
// lib/ironOptions.ts
import { IronSessionOptions } from 'iron-session';
if (!process.env.IRON_SESSION_PASSWORD)
throw new Error('IRON_SESSION_PASSWORD must be set');
const ironOptions: IronSessionOptions = {
password: process.env.IRON_SESSION_PASSWORD,
cookieName: 'session',
cookieOptions: {
secure: process.env.NODE_ENV === "production",
},
};
declare module "iron-session" {
interface IronSessionData {
address?: string | undefined;
nonce?: string | undefined;
}
}
export default ironOptions;
```
**Remember to set IRON_SESSION_PASSWORD** in your `.env.local` file for
development, and in your production environment through your hosting
provider settings. The password must be at least 32 characters long. You can
use https://1password.com/password-generator/ to generate strong passwords.
For full reference of possible options see:
https://github.com/vvo/iron-session#ironoptions
**Typing session data**
The type definition of `IronSessionData` in the example above provides a type
definition to the data passed to api functions in `req.session`. `address` and
`nonce` are used and set by UseSIWE; if you plan on storing other data in the
session, feel free to add additional types here.
For more information see:
https://github.com/vvo/iron-session#typing-session-data-with-typescript
## Setting up the API routes
### Next.js
Copy and past the following code into `pages/api/auth/[[...route]].ts`:
```ts
import { withIronSessionApiRoute } from "iron-session/next";
import ironOptions from "lib/ironOptions";
import { siweApi } from "@randombits/use-siwe/next"
export default withIronSessionApiRoute(siweApi(), ironOptions);
```
### Express.js
To add auth routes to your existing express API, add the following:
```ts
import express from "express";
import { ironSession } from "iron-session/express";
import ironOptions from "./ironOptions.js";
import { authRouter } from "@randombits/use-siwe/express";
const app = express();
// Add iron session middleware before all routes that will use session data
app.use(ironSession(ironOptions));
// Your existing api routes here...
// Add UseSIWE auth routes
app.use('/auth', authRouter());
app.listen(3001);
```
## Wrapping your application with `SiweProvider`
Any component that uses the any of the UseSIWE hooks must be wrapped with the
`SiweProvider` component. For a Next.js application we recommend doing so in
`pages/_app.tsx` like in the example below:
```ts
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { configureChains, mainnet } from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { SiweProvider } from '@randombits/use-siwe';
const { chains, provider, webSocketProvider } = configureChains(
[mainnet],
[publicProvider()],
);
const client = createClient({
autoConnect: true,
provider,
webSocketProvider,
});
export default function MyApp({ Component, pageProps }: AppProps) {
return (
);
}
```
**Important:** The `SiweProvider` must be inside a `WagmiConfig` component.
## Using the hooks
### Checking if a user is authenticated
#### Client-side
Check to see is a user is authenticated with the `useSession` hook like in the
example below:
```ts
import { useSession } from "@randombits/use-siwe";
export const AuthCheck = () => {
const { isLoading, authenticated, address } = useSession();
if (isLoading) return
Loading...
;
if (!authenticated) return Not authenticated
;
return {address} is Authenticated
;
};
```
#### Server-side
For API routes, wrap your API handler with `withIronSessionApiRoute` and check
to see if `req.session.address` is set. If a user is authenticated,
`req.session.address` will be set to their address, otherwise it will be
`undefined`.
```ts
import ironOptions from '@/lib/ironOptions'
import { withIronSessionApiRoute } from 'iron-session/next/dist'
import type { NextApiHandler } from 'next'
const handler: NextApiHandler = (req, res) => {
if (!req.session.address) return res.status(401).send("Unauthorized");
res.status(200).send(`Hello, ${req.session.address}!`);
}
export default withIronSessionApiRoute(handler, ironOptions);
```
### Signing In
Login the user by calling the `signIn` function returned by the `useSignIn`
hook:
```ts
import { useSignIn } from "@randombits/use-siwe";
const SignInButton = () => {
const { signIn, isLoading } = useSignIn();
return signIn()} disabled={isLoading}>Sign In with Ethereum;
};
```
### Signing Out
Logout the user by calling the `signOut` function returned by the `useSignOut`
hook:
```ts
import { useSignOut } from "@randombits/use-siwe";
const SignOutButton = () => {
const { signOut, isLoading } = useSignOut();
return signOut()} disabled={isLoading}>Sign Out;
};
```
# API
## Types
### UseSiweOptions
UseSIWE accepts an object of options. Currently this consists of one optional
setting:
#### Usage
```ts
const options: UseSiweOptions = {
baseUrl: "/v2/api/auth",
};
```
#### Options
- `baseUrl`, optional: The base url for the auth API endpoints that is
prepended to all requests. Defaults to: `/api/auth`
## Components
### SiweProvider
Context provider component that must wrap all components that use `useSession`,
`useSignIn`, `useSignOut`, or `useOptions` hooks.
#### Usage
```ts
import type { AppProps } from 'next/app';
import { SiweProvider } from '@randombits/use-siwe';
export default function MyApp({ Component, pageProps }: AppProps) {
return
;
}
```
#### Props
- `options`, Optional: A `UseSiweOptions` object.
## Hooks
### useSession
A hook that returns the the current state of the users session.
#### Usage
```ts
import { useSession } from "@randombits/use-siwe";
export const Component = () => {
const { isLoading, authenticated, address } = useSession();
if (isLoading) return
Loading...;
if (!authenticated) return Not Signed In;
return Hello, {address}!;
};
```
#### Return Value
Returns a `UseQueryResult` ([ref](https://tanstack.com/query/latest/docs/react/reference/useQuery))
augmented with the following:
```ts
{
authenticated: boolean;
address?: string;
nonce?: string;
} & UseQueryResult
```
### useSignIn
A hook that returns a `signIn` function that will initiate a SIWE flow, as well
as the status of that signIn process.
#### Usage
```ts
import { useSignIn } from "@randombits/use-siwe";
const SignInButton = () => {
const { signIn, isLoading } = useSignIn();
return signIn()} disabled={isLoading}>Sign In with Ethereum;
};
```
#### Options
```ts
{
onSuccess: () => void,
onError: () => void,
}
```
#### Return Value
Returns a `UseMutationResult` ([ref](https://tanstack.com/query/latest/docs/react/reference/useMutation))
augmented with the following:
```ts
{
signIn: () => void,
SignInAsync: () => Promise,
} & UseMutationResult
```
### useSignOut
A hook that returns a `signOut` function that when called will sign out the
current user and disconnect their wallet.
#### Usage
```ts
import { useSignOut } from "@randombits/use-siwe";
const SignOutButton = () => {
const { signOut, isLoading } = useSignOut();
return signOut()} disabled={isLoading}>Sign Out;
};
```
#### Options
```ts
{
onSuccess: () => void,
onError: () => void,
}
```
#### Return Value
Returns a `UseMutationResult` ([ref](https://tanstack.com/query/latest/docs/react/reference/useMutation))
augmented with the following:
```ts
{
signOut: () => void,
SignOutAsync: () => Promise,
} & UseMutationResult
```
### useOptions
A hook that simply returns the options that have been set by in the
`SiweProvider` component.
#### Usage
```ts
import { useOptions, verify } from "@randombits/use-siwe";
const verifyButton = (props) => {
const options = useOptions();
const handleClick = () => verify({
message: props.message,
signature: props.signature,
}, options);
return handleClick()}>Verify Signature;
};
```
#### Return Value
```ts
useSiweOptions
```
## Routes
### Next.js: SiweApi
A function that returns a `NextApiHandler` that will handle all auth API
routes.
#### Usage
```ts
import { withIronSessionApiRoute } from "iron-session/next";
import ironOptions from "lib/ironOptions";
import { siweApi } from "@randombits/use-siwe/next"
export default withIronSessionApiRoute(siweApi(), ironOptions);
```
#### Return Value
```ts
NextApiHandler
```
### Express.js: SiweApi
A function that returns an express `Router` that will handle all auth API
routes.
#### Usage
```ts
import express from "express";
import { ironSession } from "iron-session/express";
import ironOptions from "./ironOptions.js";
import { authRouter } from "@randombits/use-siwe/express";
const app = express();
app.use(ironSession(ironOptions));
app.use('/auth', authRouter());
app.listen(3001);
```
#### Return Value
```ts
Router
```
## Functions
### getSession
A function to retrieve the session data where using a hook doesn't make sense.
#### Usage
```ts
import { getSession } from "@randombits/use-siwe";
const addressOrNull = async () => {
const { address } = await getSession();
if (!address) return null;
return address;
};
```
#### Args
- `options?: UseSiweOptions`
#### Return Value
```ts
{
authenticated: boolean;
address?: string;
nonce?: string;
}
```
### createMessage
Returns a `SiweMessage` for the given address, chainId, and nonce.
#### Usage
```ts
import { createMessage, getMessageBody } from "@randombits/use-siwe";
const debugMessage = (address, chainId, nonce) => {
const message = createMessage({ address, chainId, nonce });
const messageBody = getMessageBody({ message });
console.log({ message, messageBody });
};
```
#### Args
- `args: MessageArgs`
```ts
type MessageArgs = {
address: string,
chainId: number,
nonce: string,
};
```
#### Return Value
```ts
SiweMessage
```
### getMessageBody
Returns a message ready to be signed according with the type defined in the
SiweMessage object.
#### Usage
```ts
import { createMessage, getMessageBody } from "@randombits/use-siwe";
const debugMessage = (address, chainId, nonce) => {
const message = createMessage({ address, chainId, nonce });
const messageBody = getMessageBody({ message });
console.log({ message, messageBody });
};
```
#### Args
- `args: { message: SiweMessage }`
#### Return Value
```ts
string
```
### verify
Takes a message and a signature as arguments and attempts to verify them using
the auth API. A successful verification will create a session for the user.
#### Usage
```ts
import { verify } from "@randombits/use-siwe";
const verifyButton = (props) => {
const handleClick = () => {
const success = verify({
message: props.message,
signature: props.signature,
});
if (!success) return console.error("VERIFICATION FAILED");
console.log("SIGNATURE VERIFIED");
};
return handleClick()}>Verify Signature;
};
```
#### Args
- `args: VerifyArgs`
- `options?: UseSiweOptions`
```ts
type VerifyArgs = {
message: SiweMessage,
signature: string,
};
```
#### Return Value
```ts
boolean
```
### signOut
A function to sign out the user where using a hook doesn't make sense.
#### Usage
```ts
import { signOut } from "@randombits/use-siwe";
// Logout a user after 1 hour
setTimeout(async () => {
await signOut();
window.location.href = "/session-expired";
}, 60 * 60 * 1000);
```
#### Args
- `options?: UseSiweOptions`
#### Return Value
```ts
Promise
```