Skip to content
This repository has been archived by the owner on Jan 5, 2024. It is now read-only.

Commit

Permalink
docs: add custom auth docs (#1117)
Browse files Browse the repository at this point in the history
* docs: add custom auth docs

* chore: update slugs

* chore: remove dup import

* chore: fix user formatting

* chore: update quickstart section

* fix: remove duplicate import on auto save

* chore: update wording

* chore: update custom auth config, fix typos, and update title

* chore: fix custom auth docs from feedback

* fix: remove dup import

* chore: update diagrams

* chore: add clarification on quickstart link

* chore: minor tweaks
  • Loading branch information
ElasticBottle authored Dec 14, 2023
1 parent 97ceb3f commit 67563ea
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,110 @@ slug: /embedded-wallet/custom-auth
title: Use your own auth
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Tabs from "@theme/Tabs";
import QuickstartCard from "@components/QuickstartCard";

By default, the embedded wallet service handles two things: auth, and spinning up crypto wallets tied to the auth. We require
valid authentication to ensure a wallet is created for the right person.
If you already have your own auth and only want to spin up wallets, we offer a simple way to hook up any **OpenID Connect ("OIDC") compatible** auth to create embedded wallets.
If you already have your own auth and only want to spin up wallets, we offer a simple way to hook up any auth to create embedded wallets.

## How it works

We offer two kinds of custom auth. One that is based on the OIDC standard, and one that is is based on you having you bring your own auth server.

### Bring your own auth server

- You have your own auth server that you use to authenticate users
- When a user logs in, you are able to generate a public identifier that allows you to identify the user.
- You can pass this identifier to the embedded wallet to generate a wallet for the user.
- When verifying the user, we will hit an endopint that you provide to verify the user's identity.
- We will then generate a wallet for the user if the provided payload is valid.

### OIDC

- An OIDC auth system has a public-private keypair, where the private key is used to sign auth tokens
- The public key is uploaded to a public URL in JWKS format. The standard location is `https://{domain}.com/.well-known/jwks.json`
- When a user logs in, a JWT token called the idToken is generated and signed by the private key. The OIDC spec provides an interface for fields that are used in this token.
- We use the public key to verify that the JWT was signed correctly, and proceed to generate a wallet based on the `sub` (user identifier) value of the idToken.
- This JWT is then passed to the embedded wallet to generate a wallet for the user.
- We will verify the JWT against the public key to verify that the JWT was signed correctly. Upon successful verification, we will proceed to generate a wallet based on the `sub` (user identifier) value of the idToken.

## Configuration Setup

In your API key settings, click edit, look for "Custom Auth" and provide the following values:

### Bring your own auth server

- An endpoint that we can hit to verify the user's identity
- This endpoint should accept a POST request with a JSON body containing the following fields:
- `payload`: This will correspont to the public identifier that was generated for your user.
- The endpoint should return a JSON body containing the following fields:
- `userId`: A uid for the user. Note that you can only create one wallet per `userId` at this point
- `email` (optional): If provided, the user will be able to access the same account outside of the platform for things like private key export // using with wallet connect etc.
- `exp` (optional): An expiration date for the user's wallet session. By default a session is 7 days long.
- A list of custom headers (optional)
- These headers will be sent with every request to your verification endpoint. You can use these to authenticate the request.

### OIDC

- The URL of the JWKS file (public key)
- This is used to verify the token was signed by you.
- The `aud` value of the idToken
- This is used to verify that thirdweb is the intended user of the token

## Authenticating a user

Once you've logged in with your own auth, you can pass the user's JWT to the embedded wallet to authenticate and connect.
Once you've logged in with your own auth, you can pass the user's detail to the embedded wallet to authenticate and connect.

### Bring your own auth server


<Tabs>
<TabItem value="react" label="React & React Native">

In React and React Native, the `useEmbeddedWallet()` hook handles authentication and connection states.

```typescript
import { useEmbeddedWallet } from "@thirdweb-dev/react"; // or /react-native

const embeddedWallet = useEmbeddedWallet();

const handlePostLogin = async (jwt: string) => {
await embeddedWallet.connect({
strategy: "auth_endpoint",
payload,
});
};
```

</TabItem>

<TabItem value="typescript" label="Other Typescript Frameworks">

In other frameworks, use your own instance of the wallet to authenticate and connect.

```typescript
import { EmbeddedWallet } from "@thirdweb-dev/wallets";
import { Goerli } from "@thirdweb-dev/chains";

const embeddedWallet = new EmbeddedWallet({
chain: Goerli, // chain to connect to
clientId: "YOUR_CLIENT_ID", // Your thirdweb client ID
});

const authResult = await embeddedWallet.authenticate({
strategy: "auth_endpoint",
payload,
});

const walletAddress = await embeddedWallet.connect({ authResult });
```

</TabItem>
</Tabs>


### OIDC

<Tabs>
<TabItem value="react" label="React & React Native">
Expand All @@ -51,7 +128,7 @@ const handlePostLogin = async (jwt: string) => {

</TabItem>

<TabItem value="typescript" label="Other Frameworks">
<TabItem value="typescript" label="Other Typescript Frameworks">

In other frameworks, use your own instance of the wallet to authenticate and connect.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,135 +3,170 @@ slug: /embedded-wallet/custom-auth-server
title: Custom Auth Server
---

import TabItem from "@theme/TabItem";
import Tabs from "@theme/Tabs";

# Create a custom auth server

Learn how to integrate your auth backend with our embedded wallets solution so you can onboard your users into web3 seamlessly.
Learn how to integrate your auth backend with our embedded wallets solution so you can onboard your users into web3 seamlessly.

This guide will show you how to create your own Auth Server. By doing so, you can have full control over user authentication and data security. This allows you to ensure that your application meets specific compliance requirements while also providing a customized sign-in experience.
This guide will show you how to create your own Auth Server that is compatible with the `auth_endpoint` strategy. By doing so, you can have full control over user authentication and data security. This allows you to ensure that your application meets specific compliance requirements while also providing a customized sign-in experience.

:::caution
This guide is simplified for demonstration purposes and is not ready for production use. When modifying it for production, secure your endpoints and avoid hard-coding secrets or sensitive information. We recommend using environment variables and secret managers.
:::

### Setup
## 5 minute quickstart

1. Create a new directory for your project and navigate to it in your CLI
1. Navigate to Wallets > [Embedded Wallets](https://thirdweb.com/dashboard/wallets/embedded) in the thirdweb dashboard.
2. Create a thirdweb API key if you don't have one or select an existing key to use for this project. [Learn more about API keys.](https://portal.thirdweb.com/api-keys)

```bash
mkdir jwt-auth-server
cd jwt-auth-server
```
![Embedded wallet dashboard with create key displayed](../assets/ew-create-key.png)

2. Initialize a new Node.js application
3. Allowlist domain or bundle ids in Access Restrictions.
4. Navigate to the Configuration view and enable **Custom Auth Endpoint**

```bash
npm init -y
![Configuration view for embedded wallet](../assets/ew-custom-auth-config.png)

yarn init -y
```
5. Set the Auth Endpoint URL to `https://embedded-wallet.thirdweb.com/api/2023-11-30/embedded-wallet/auth/test-custom-auth-endpoint` for testing purposes. You will replace this later with your own auth server endpoint to verify the `payload`.
6. Save the configuration.
7. Copy the client ID.
8. In your preferred thirdweb client SDK, pass the payload you retrieved from logging in to the server.

3. Install the necessary packages
You can now auth into the wallet and use it to sign transactions like so (see [use your own auth for more](/embedded-wallet/custom-auth)):

```bash
npm install express jsonwebtoken
```
<Tabs>
<TabItem value="react" label="React & React Native">

### **Generate RSA Key Pair:**
In React and React Native, the `useEmbeddedWallet()` hook handles authentication and connection states.

1. To generate a private and a public key run
```typescript
import { useEmbeddedWallet } from "@thirdweb-dev/react"; // or /react-native

```bash
ssh-keygen -t rsa -b 2048 -m PEM -f keys/rsa.key
```
const embeddedWallet = useEmbeddedWallet();

2. To create the output file run
const handlePostLogin = async () => {
await embeddedWallet.connect({
strategy: "auth_endpoint",
// in production this would be your public identifier for the user
payload: JSON.stringify({ userId:"ANY_RANDOM_ID_HERE" }),
encryptionKey: "ANY_RANDOM_STRING_HERE"
});
};
```

```bash
openssl rsa -in keys/rsa.key -pubout -outform PEM -out keys/rsa.key.pub
```
</TabItem>
<TabItem value="typescript" label="Other Typescript Frameworks">

### **Convert Public Key to JSON Web Key Set (JWKS):**
In other frameworks, use your own instance of the wallet to authenticate and connect.

1. Display the public key:
```typescript
import { EmbeddedWallet } from "@thirdweb-dev/wallets";
import { Goerli } from "@thirdweb-dev/chains";

```bash
cat keys/rsa.key.pub
```
const embeddedWallet = new EmbeddedWallet({
chain: Goerli, // chain to connect to
clientId: "YOUR_CLIENT_ID", // Your thirdweb client ID
});

const authResult = await embeddedWallet.authenticate({
strategy: "auth_endpoint",
payload: JSON.stringify({ userId:"ANY_RANDOM_ID_HERE" }),
encryptionKey: "ANY_RANDOM_STRING_HERE"=
});

const walletAddress = await embeddedWallet.connect({ authResult });
```

2. Copy the displayed public key.
3. Convert your public key to a JWK using an online JWK Creator tool. We recommend using [JWK Creator by Russel Davies](https://github.com/russelldavies/jwk-creator).
</TabItem>
</Tabs>

1. Paste the public key, set Key ID as `0` (arbitrary string, must match when signing JWT), and then note down the generated JWK.
A persistent, cross-platform wallet is now created for your user!

![JWK Creator tool by Russel Davies showing key id of 0](../assets/jwk-creator-tool.png)
Of course, you would use your own auth server instead of the one we provided. The rest of this guide will show you how to create your own auth server.

4. Create a `jwks.json` in the project root and place the generated JWK in a `keys` array.
### Setup

The following steps will show you how to create a simple auth server that can be used with the embedded wallet.

At a high level, the auth server will:

1. Handle login for the user into your application.
2. Have a way to get a public identifier for the user.
3. Have an endpoint to verify the public identifier and return some basic information about the user

Steps 1 and 2 are up to you to implement. You can use any auth strategy you want.

The endpoint in step 3 is what your register as your auth endpoint on the thirdweb dashboard.

Here's a high level diagram:
![custom auth flow diagram](../assets/ew-custom-auth-flow.png)

1. Create a new directory for your project and navigate to it in your CLI

```bash
mkdir custom-auth-server
cd custom-auth-server
```

2. Initialize a new Node.js application

```bash
{
"keys": [
{
... JWK ...
}
]
}
npm init -y

yarn init -y
```

### **Create the Server:**

1. In the `jw-auth-server` directory, create a file at the root named `server.js` and paste the following:
1. In the `custom-auth-server` directory, create a file at the root named `server.js` and paste the following:

```jsx
const express = require("express");
const fs = require("fs");
const jwt = require("jsonwebtoken");

const app = express();
const PORT = process.env.PORT || 3000;

const PRIVATE_KEY = fs.readFileSync("./keys/rsa.key", "utf8");
const jwks = require("./jwks.json");

const users = [
{ id: 1, email: "[email protected]", password: "password123" },
];

app.use(express.json());

// This is what your app calls to login a user and get a public identifier for the user (otherwise known as the payload)
app.post("/login", (req, res) => {
const { email, password } = req.body;
const user = users.find(
(u) => u.email === email && u.password === password,
);
if (!user) return res.status(401).send({ message: "Invalid credentials" });

const payload = {
iss: "http://your-domain.com",
sub: user.id.toString(),
aud: "EpicGame",
email: user.email,
exp: Math.floor(Date.now() / 1000) + 3600,
};

const token = jwt.sign(payload, PRIVATE_KEY, {
algorithm: "RS256",
keyid: "0",
});

res.send({ token });
res.send({ payload: user.id });
});
// This is a sample endpoint that yuou would register on the thirdweb dashboard for us to verify the payload
app.get("/thirdweb-will-call-this", (req, res) => {
const { payload } = req.body;
if (!payload) return res.status(401).send({ message: "Invalid credentials" });

app.get("/.well-known/jwks.json", (req, res) => {
res.json(jwks);
// you would write your own logic here to verify the payload here
const user = users.find((u) => u.id === payload);
if (!user) return res.status(401).send({ message: "Invalid credentials" });

// once the user is successfully verified, you can return the following field
return res.send({
userId: user.id,
// the last two fields here are optional
email: user.email,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30,
});
});

app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
```

2. Replace `http://your-domain.com` with the actual domain for the application.

### **Test Locally**

1. Start the server:
Expand All @@ -146,36 +181,10 @@ This guide is simplified for demonstration purposes and is not ready for product
curl -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"email": "[email protected]", "password": "password123"}'
```

3. Test JWKS:

```bash
curl http://localhost:3000/.well-known/jwks.json
```

### **Deploy**

To deploy the server, you can use use services such as [Zeet](https://zeet.co/) or [Docker](https://www.docker.com/).

Once deployed, replace `http://localhost:3000` in the JWT payload with your actual domain

### **Integrate Embedded Wallets**

1. Navigate to Wallets > [Embedded Wallets](https://thirdweb.com/dashboard/wallets/embedded) in the thirdweb dashboard.
2. Create a thirdweb API key if you don’t have one or select an existing key to use for this project. [Learn more about API keys.](https://portal.thirdweb.com/api-keys)

![Embedded wallet dashboard with create key displayed](../assets/ew-create-key.png)

3. Allowlist domain or bundle ids in Access Restrictions.
4. Navigate to the Configuration view and enable **Custom JSON Web Token**

![Configuration view for embedded wallet](../assets/ew-configuration.png)

5. Set the JWKS URI to `your-domain/.well-known/jwks.json`
6. Set the AUD to `EpicGame` or the value you set as the aud in the `server.js` file.

![Options for EW Configuration](../assets/ew-configuration-opt.png)

7. Copy the client ID.
8. In your preferred thirdweb client SDK, pass the JWT you retrieved from logging in to the server.

A persistent, cross-platform wallet is now created for your user.
Refer top the [quickstart above](#5-minute-quickstart) to integrate the embedded wallet into your application.
Loading

1 comment on commit 67563ea

@vercel
Copy link

@vercel vercel bot commented on 67563ea Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.