Giter Site home page Giter Site logo

random-bits-studio / use-siwe Goto Github PK

View Code? Open in Web Editor NEW
24.0 3.0 3.0 374 KB

The easiest way to add Sign-in with Ethereum to your app.

License: MIT License

Shell 0.53% TypeScript 99.47%
auth authentication ethereum next nextjs session siwe wagmi web3 ironsession

use-siwe's Introduction

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

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:

// 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:

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:

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:

// 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 (
    <WagmiConfig client={client}>
      <SiweProvider>
        <Component {...pageProps} />
      </SiweProvider>
    </WagmiConfig>
  );
}

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:

import { useSession } from "@randombits/use-siwe";

export const AuthCheck = () => {
  const { isLoading, authenticated, address } = useSession();

  if (isLoading) return <p>Loading...</p>;
  if (!authenticated) return <p>Not authenticated</p>;
  return <p>{address} is Authenticated</p>;
};

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.

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:

import { useSignIn } from "@randombits/use-siwe";

const SignInButton = () => {
  const { signIn, isLoading } = useSignIn();
  return <button onClick={() => signIn()} disabled={isLoading}>Sign In with Ethereum</button>;
};

Signing Out

Logout the user by calling the signOut function returned by the useSignOut hook:

import { useSignOut } from "@randombits/use-siwe";

const SignOutButton = () => {
  const { signOut, isLoading } = useSignOut();
  return <button onClick={() => signOut()} disabled={isLoading}>Sign Out</button>;
};

API

Types

UseSiweOptions

UseSIWE accepts an object of options. Currently this consists of one optional setting:

Usage

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

import type { AppProps } from 'next/app';
import { SiweProvider } from '@randombits/use-siwe';

export default function MyApp({ Component, pageProps }: AppProps) {
  return <SiweProvider>
    <Component {...pageProps} />
  </SiweProvider>;
}

Props

  • options, Optional: A UseSiweOptions object.

Hooks

useSession

A hook that returns the the current state of the users session.

Usage

import { useSession } from "@randombits/use-siwe";

export const Component = () => {
  const { isLoading, authenticated, address } = useSession();

  if (isLoading) return <div>Loading...</div>;
  if (!authenticated) return <div>Not Signed In</div>;
  return <div>Hello, {address}!</div>;
};

Return Value

Returns a UseQueryResult (ref) augmented with the following:

{
  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

import { useSignIn } from "@randombits/use-siwe";

const SignInButton = () => {
  const { signIn, isLoading } = useSignIn();
  return <button onClick={() => signIn()} disabled={isLoading}>Sign In with Ethereum</button>;
};

Options

{
  onSuccess: () => void,
  onError: () => void,
}

Return Value

Returns a UseMutationResult (ref) augmented with the following:

{
  signIn: () => void,
  SignInAsync: () => Promise<void>,
} & UseMutationResult

useSignOut

A hook that returns a signOut function that when called will sign out the current user and disconnect their wallet.

Usage

import { useSignOut } from "@randombits/use-siwe";

const SignOutButton = () => {
  const { signOut, isLoading } = useSignOut();
  return <button onClick={() => signOut()} disabled={isLoading}>Sign Out</button>;
};

Options

{
  onSuccess: () => void,
  onError: () => void,
}

Return Value

Returns a UseMutationResult (ref) augmented with the following:

{
  signOut: () => void,
  SignOutAsync: () => Promise<void>,
} & UseMutationResult

useOptions

A hook that simply returns the options that have been set by in the SiweProvider component.

Usage

import { useOptions, verify } from "@randombits/use-siwe";

const verifyButton = (props) => {
  const options = useOptions();
  const handleClick = () => verify({
    message: props.message,
    signature: props.signature,
  }, options);

  return <button onClick={() => handleClick()}>Verify Signature</button>;
};

Return Value

useSiweOptions

Routes

Next.js: SiweApi

A function that returns a NextApiHandler that will handle all auth API routes.

Usage

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

NextApiHandler

Express.js: SiweApi

A function that returns an express Router that will handle all auth API routes.

Usage

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

Router

Functions

getSession

A function to retrieve the session data where using a hook doesn't make sense.

Usage

import { getSession } from "@randombits/use-siwe";

const addressOrNull = async () => {
  const { address } = await getSession();
  if (!address) return null;
  return address;
};

Args

  • options?: UseSiweOptions

Return Value

{
  authenticated: boolean;
  address?: string;
  nonce?: string;
}

createMessage

Returns a SiweMessage for the given address, chainId, and nonce.

Usage

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
type MessageArgs = {
  address: string,
  chainId: number,
  nonce: string,
};

Return Value

SiweMessage

getMessageBody

Returns a message ready to be signed according with the type defined in the SiweMessage object.

Usage

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

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

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 <button onClick={() => handleClick()}>Verify Signature</button>;
};

Args

  • args: VerifyArgs
  • options?: UseSiweOptions
type VerifyArgs = {
  message: SiweMessage,
  signature: string,
};

Return Value

boolean

signOut

A function to sign out the user where using a hook doesn't make sense.

Usage

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

Promise<void>

use-siwe's People

Contributors

aj-may avatar codfish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

drolu codfish omahs

use-siwe's Issues

The automated release is failing 🚨

🚨 The automated release from the main branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can fix this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the main branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here are some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


No npm token specified.

An npm token must be created and set in the NPM_TOKEN environment variable on your CI environment.

Please make sure to create an npm token and to set it in the NPM_TOKEN environment variable on your CI environment. The token must allow to publish to the registry https://registry.npmjs.org/.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.