React
Once you've setup an Auth API for your application, you can use the @thirdweb-dev/react
and @thirdweb-dev/auth
packages to add Auth support to your React apps.
Getting Started
To add support for Auth to your React apps, you'll first need to install the following packages:
- npm
- Yarn
npm install @thirdweb-dev/react @thirdweb-dev/sdk @thirdweb-dev/auth ethers@5
yarn add @thirdweb-dev/react @thirdweb-dev/sdk @thirdweb-dev/auth ethers@5
Now we'll need to wrap our app with the ThirdwebProvider
which stores the necessary context for Auth, and we'll provide some configuration with the authConfig
option so that our frontend knows how to communicate with our Auth API.
import { ThirdwebProvider } from "@thirdweb-dev/react";
export default function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider
// Required configuration for the provider, but doesn't affect Auth.
activeChain="ethereum"
authConfig={{
// Set this to your domain to prevent phishing attacks
domain: "example.com",
// The URL of your Auth API
authUrl: "/api/auth",
}}
>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
Connect wallet & login button
The simplest way to add an Auth flow to our app is to use the ConnectWallet
button. This button detects the authConfig
on our ThirdwebProvider
and will automatically prompt the user to connect their wallet with MetaMask, Coinbase Wallet, or WalletConnect, and then login to our app with Auth.
import { ConnectWallet } from "@thirdweb-dev/react";
export default function Home() {
return <ConnectWallet />;
}
Connect wallet & auth hooks
For a more customized setup with custom UI or logic, we can instead use the wallet connection and Auth hooks provided by the @thirdweb-dev/react
package.
import {
useAddress,
useMetamask,
useLogin,
useLogout,
useUser,
} from "@thirdweb-dev/react";
export default function Home() {
const address = useAddress();
const connect = useMetamask();
const { login } = useLogin();
const { logout } = useLogout();
const { user, isLoggedIn } = useUser();
return (
<div>
{isLoggedIn ? (
<button onClick={() => logout()}>Logout</button>
) : address ? (
<button onClick={() => login()}>Login</button>
) : (
<button onClick={() => connect()}>Connect</button>
)}
<pre>Connected Wallet: {address}</pre>
<pre>User: {user?.address || "N/A"}</pre>
</div>
);
}
Here, we use the useMetaMask
hook to prompt our users to connect their metamask wallets, and then we use the useLogin
hook to login to our app with Auth. Then, we can access and display the logged in user with the useUser
hook, and we can logout the user with the useLogout
hook.
For a full list of wallet connection hooks, check out our React SDK documentation.
Usage
Check if a user is logged in
On the frontend, you may want to check whether or not the user is logged in to conditionally render certain parts of your app. You can do this with the isLoggedIn
value from the useUser
hook:
import { isLoggedIn } from "@thirdweb-dev/react";
export default function Home() {
const { isLoggedIn } = useUser();
return (
<div>
{isLoggedIn ? (
<p>Here's some content only visible to logged in users.</p>
) : (
<p>Here's some content only visible to logged out users.</p>
)}
</div>
);
}
Accessing user & session data
Once a user is logged in, you can access their user data and session data with the useUser
hook. The user
value will be null
if the user is not logged in, but once they're logged in, it will contain the address
of the logged in user, the session context
set by the backend on login, and any extra user-associated data
sent by the backend.
import { useUser } from "@thirdweb-dev/react";
export default function Home() {
const { user } = useUser();
return (
<div>
<p>Logged in user: {user?.address}</p>
<p>Session context: {user?.context}</p>
<p>Extra user data: {user?.data}</p>
</div>
);
}
You can think of user.context
as a way for the backend to store arbitrary session data on the JWT, like a user's role or permissions. This data is then accessible on the frontend and doesn't change until a user has to login again.
Meanwhile, user.data
is a way for the backend to send the client any data associated with the current user like their name, profile picture, and other information. Since this data isn't stored on the JWT, it can change at any time.
Using loading states
When using the useUser
hook, you may want to show a loading state while the user's session is being fetched from the backend. You can do this by checking the isLoading
value from the hook:
import { useUser } from "@thirdweb-dev/react";
export default function Home() {
const { user, isLoading } = useUser();
return (
<div>
{isLoading ? <p>Loading...</p> : <p>Logged in user: {user?.address}</p>}
</div>
);
}
Similarly, the useLogin
and useLogout
hooks also have loading states to indicate when a request to the backend is being made.
import { useLogin, useLogout } from "@thirdweb-dev/react";
export default function Home() {
const { login, isLoading: isLoggingIn } = useLogin();
const { logout, isLoading: isLoggingOut } = useLogout();
return (
<div>
{isLoggingIn ? (
<p>Logging in...</p>
) : (
<button onClick={() => login()}>Login</button>
)}
{isLoggingOut ? (
<p>Logging out...</p>
) : (
<button onClick={() => logout()}>Logout</button>
)}
</div>
);
}
Customizing the login message
The login
function automatically uses some default values for the necessary fields of the EIP4361/CAIP122 specifications.
However, if you want full access to customize these field values, you can overwrite them by passing an object to the login
function:
import { useLogin } from "@thirdweb-dev/react";
export default function Home() {
const { login } = useLogin();
async function handleLogin() {
// All of these fields are optional
login({
uri: "https://example.com",
statement: "Please agree to our terms of service!",
chainId: "123",
nonce: "123"
version: "1",
resources: ["https://tos.example.com"],
})
}
return (
<div>
<button onClick={handleLogin}>Login</button>
</div>
);
}
Handling errors on login
Since logging in requires the user to sign a message, the login
fuction is asynchronous and may throw an error if the user rejects the message. You can handle this error case by using a try/catch
block:
import { useLogin } from "@thirdweb-dev/react";
export default function Home() {
const { login } = useLogin();
async function handleLogin() {
try {
await login();
} catch (err) {
console.error(err);
}
}
return (
<div>
<button onClick={handleLogin}>Login</button>
</div>
);
}
Cross-origin requests & cookies
Auth uses cookies to securely store JWTs in the browser context. This means that if our Auth backend and frontend aren't on the same domain, we may run into CORS issues.
Specifically, on the frontend you don't have access to cookies but they need to get sent to the backend in order for your users to authenticate to your API. This is solved because the browser automatically sends cookies on requests to your backend if your frontend and backend are on the same URL.
When your frontend and backend are on different domains, you may need to manually specify that your request should include cookies by setting the credentials
option in your request to include
:
async function getData() {
const res = await fetch("/api/data", {
// Include all cookies in this request
credentials: "include",
});
const data = await res.json();
}