# Announcing MCP Authentication: secure your MCP servers with PropelAuth

### Multi-tenant applications

In the software world, a **tenant** is a collection of users that use a product together. This could be as few as a single user or as many as a large enterprise.

**Single-tenancy** is when each tenant gets their own dedicated application, database, and infrastructure. This contrasts with **multi-tenancy** where tenants share resources.

.png)

In either case, a common goal is to isolate tenants from one another so they cannot view or modify each other’s data. In the single tenant model, this happens naturally because they are completely separated. In the multi-tenant model, this has to be enforced at either the application or database level.

Why choose a multi-tenant application even with this extra bit of work? There are some obvious advantages, like not needing to maintain or pay for separate infrastructure for each customer. In this post, we’ll show how to build a multi-tenant application using Next.js, Prisma, and PropelAuth.

### What are we going to build?

We’ll build a simple B2B application where each user can make "posts" within a tenant. All users in a tenant should be able to read their own posts, and no one outside the tenant should be able to view or access them.

In our B2B product, our tenants are **Organizations**, which we’ll see more on later. The final product will look like this:

The final product

Where everyone in a tenant can view and make posts that only they can see.

### How do we deal with users in multiple tenants?

If a user is only in one tenant, it’s pretty obvious which tenant’s data we should show. But what happens when users are in more than one tenant?

In practice, this problem comes up often. GitHub allows users to be in multiple organizations at once. Similarly, you can be in as many Slack workspaces as you want, and you can have multiple Google/Twitter accounts that you toggle between.

How you approach this problem depends on your application but there’s a couple schools of thought:

- Each tenant should have its own unique URL (e.g. **https://tenant.example.com** or **https://example.com/tenant/**)
- There’s only one url (e.g. **https://app.example.com**) and the user selects which tenant to view in the UI with a dropdown menu or something similar.

For more details on which approach you choose, we've written about the pros and cons of each [here](/content/post/customer-specific-urls-in-saas-applications?ref=propelauth.mymidnight.blog). In this example, we’ll assume that users can view their tenant’s posts by going to a URL where their tenant’s name is in the path, like **https://example.com/tenant/**.

### Checking for a path parameter in Next.js

First, we’ll create a React hook that can tell us which tenant the user is currently viewing by inspecting the URL’s path. Start by creating a new Next.js application

```shell
yarn create next-app --typescript
```

Next.js has support for [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes?ref=propelauth.mymidnight.blog) that make this pretty straightforward. A file created at **pages/org/[orgName]/posts.js** will respond to any route like **/org/something/posts** or **/org/somethingelse/posts.**

If we write the following code in that file:

```tsx
import {NextPage} from "next";
import {useRouter} from 'next/router'

const Posts: NextPage = () => {
    const router = useRouter()
    const {orgName} = router.query
    return <div>You are viewing {orgName}</div>
}

export default Posts
```

We’ll be able to see which tenant the user is attempting to view, based on their URL.

### Verifying the user is in a given tenant

We don’t currently have a concept of users, let alone tenants. We wouldn’t want just anyone to be able to view all the posts for the “123” organization, or any organization for that matter.

This is where [PropelAuth](/content/?ref=propelauth.mymidnight.blog) comes in. PropelAuth is an auth service designed for multi-tenant and B2B use cases. It includes self-service UIs for each tenant/organization to manage themselves. Our users will be able to sign up, create tenants, and invite their co-workers, without us writing any code for it.

.png)

The [Getting Started Guide](https://docs.propelauth.com/getting-started/?ref=propelauth.mymidnight.blog) will walk you through how to configure your user’s auth experience, from the look and feel of the UI to the login options you give to your users to the organization options for those same users. This [demo](https://demo.propelauth.com/?ref=propelauth.mymidnight.blog) shows some of the configuration options available.

After configuring, our users can now sign up, log in, create tenants, etc - on their own. All we have to do is check to see if they are in the tenant they are trying to access. We can do this with [PropelAuth’s React library](https://docs.propelauth.com/reference/frontend-apis/nextjs/?ref=propelauth.mymidnight.blog).

```shell
yarn add @propelauth/react
```

Then, in **pages/_app.js**, we wrap our application with an **AuthProvider**. The **AuthProvider** reaches out to our PropelAuth instance and fetches our current user’s metadata, if they are logged in. You’ll need your authUrl which you can find in your dashboard under **Frontend Integration**.

```tsx
import {AuthProvider} from "@propelauth/react";

function MyApp({ Component, pageProps }: AppProps) {
    return <AuthProvider authUrl={process.env.NEXT_PUBLIC_PROPELAUTH_AUTH_URL}>
        <Component {...pageProps} />
    </AuthProvider>
}
```

And that’s it, we can now access our user’s information anywhere in our application. We can either use higher order functions like [**withAuthInfo**](https://docs.propelauth.com/reference/frontend-apis/react/?ref=propelauth.mymidnight.blog#withauthinfo) or hooks like [**useAuthInfo**](https://docs.propelauth.com/reference/frontend-apis/react/?ref=propelauth.mymidnight.blog#useauthinfo). Let’s update our **/org/[orgName]/posts.js** file to check if the user is in orgName.

```tsx
const useOrgMembershipByPathParam = () => {
    const router = useRouter()
    const {orgName} = router.query
    // gets authentication info for the current user
    const authInfo = useAuthInfo()

// Loading only happens on initial page load
    if (authInfo.loading) {
        return {status: "loading"}
    } else if (typeof orgName !== "string") {
        return {status: "user-not-in-org"}
    }

// orgHelper makes it easy to perform common information about orgs for our
    //   current user, like checking if they are in an org by its name/ID
    // Users that are not logged in will return null as well
    const org = authInfo.orgHelper?.getOrgByName(orgName);
    if (org) {
        // This is the case where the user is in the organization, we'll return the org and an access token
        return {
            status: "user-in-org",
            org,
            accessToken: authInfo.accessToken,
        }
    } else {
        return {status: "user-not-in-org"}
    }
}

const Posts: NextPage = () => {
    const orgMembership = useOrgMembershipByPathParam();

if (orgMembership.status === "loading") return <div>Loading...</div>
    else if (orgMembership.status === "user-not-in-org") return <div>Not found</div>

return <div>You are viewing {orgMembership.org.orgName}</div>
}
```

For any tenants / organizations our users are not a member of, they will get a “Not found” error.

Note that this is just a frontend check. Frontend checks are important for **usability** but not **security**. If we fetch all posts for a tenant and then decide not to show it on the frontend, any user can just check their network traffic to see the data.

We still do want to check this so that a user that happens upon the wrong page is given a reasonable error message, but now let’s look at how we can make **authenticated** requests to our backend.

### Making authenticated requests to our backend

In order to make an **authenticated** request, we need to provide our backend with some **verifiable** piece of information that proves our user is who they say they are. PropelAuth provides [access tokens](https://docs.propelauth.com/overview/access-token/?ref=propelauth.mymidnight.blog) for this, which our backend can verify without making any external requests. We already pass back an access token for our user from **useOrgMembershipByPathParam** so let’s use it.

```tsx
const Posts: NextPage = () => {
    const orgMembership = useOrgMembershipByPathParam();
    const [posts, setPosts] = useState<Post[] | null>(null)

// Fetch whenever the status, orgId, or access token changes
    useEffect(() => {
        if (orgMembership.status === "user-in-org") {
            fetchPosts(orgMembership.org.orgId, orgMembership.accessToken).then((data) => {
                setPosts(data)
            })
        }
    }, [orgMembership.status, orgMembership.org?.orgId, orgMembership.accessToken])

if (orgMembership.status === "loading") return <div>Loading...</div>
    else if (orgMembership.status === "user-not-in-org") return <div>Not found</div>
    else if (!posts) return <div>Loading...</div>

return (
        <Container>
            <h1>Posts</h1>
            {posts.map(post => <IndividualPost key={post.post_id} post={post}/>)}
            <CreatePost/>
        </Container>
    )
}

export default Posts
```

We basically call **fetchPosts** as our orgMembership changes and render the pages. For brevity, we’ll leave out the styling of the post itself. As for the fetchPosts method itself:

```tsx
async function fetchPosts(orgId: string, accessToken: string) {
    // Pass the orgId in as a query parameter
    const queryParams = new URLSearchParams({orgId: orgId}).toString()
    const response = await fetch(`/api/post?${queryParams}`, {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
            // Pass the access token in an Authorization header
            "Authorization": `Bearer ${accessToken}`
        },
    })
    return await response.json()
}
```

Since our backend API isn’t customer facing, we can pass in our orgId however we want. As we did above, we can use a query parameter, continue using path parameters, put it in the body of a post request, etc.

Saving posts is very similar, we just need a basic textarea for the text itself and then we’ll use the following function to save the post:

```tsx
async function savePost(text: string, orgId: string, accessToken: string) {
    const qs = new URLSearchParams({"org_id": orgId})
    const url = `/api/post?${qs.toString()}"
    await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${accessToken}`
        },
        body: JSON.stringify({"text": text}),
    })
}
```

We now have everything we need on the frontend—the only problem is we don’t have a backend yet. Let’s fix that using Next.js’ built in API routes.

### Creating our backend with Next.js API routes and Prisma

Our backend wouldn’t be very useful without some way to store the posts somewhere. We will setup Prisma, a popular TypeScript ORM, which will manage all our database operations, including migrations.

```json
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}
```

Afterwards, you should have a new file in your repo, **prisma/schema.prisma**

```json
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}
```

This is where we will define our schema. The schema itself is fairly straightforward. We want to have a post which contains some text to display. The post should be associated with a tenant (organization) and a user.

```json
model Post {
  post_id     String @id @default(uuid())
  org_id      String
  user_id     String
  text        String
}
```

Nothing too fancy, each post has a **post_id** that defaults to a new UUID. It contains IDs for the tenant ( **org_id**) and user ( **user_id**). And finally, it contains our text. Now we can migrate our database, which just means to create or update it to the most recent schema:

```shell
npx prisma migrate dev --name init

...

Your database is now in sync with your schema.
```

In the future, if we want to add more fields like a timestamp, we can update our schema and migrate will take care of the rest.

### Using Next.js API routes

Next.js API Routes allow you to create your own APIs within Next.js. As Next.js themselves puts it:

Any file inside the folder **pages/api** is mapped to **/api/*** and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size.

Create a new file **pages/api/post.ts** which will respond to requests at **/api/post**

We need to check the method first and decide how to handle the request:

```tsx
import type {NextApiRequest, NextApiResponse} from 'next'

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<GetHandlerResponse | PostHandlerResponse | string>
) {
    if (req.method === "GET") {
        await getHandler(req, res);
    } else if (req.method === "POST") {
        await postHandler(req, res);
    } else {
        res.status(405).send("Method not allowed")
    }
}
```

We’ll respond to **GET** requests by listing all posts with an organization/tenant, just like the frontend expects:

```tsx
import {Post} from '@prisma/client'

export type GetHandlerResponse = Post[]

async function getHandler(
    req: NextApiRequest,
    res: NextApiResponse<GetHandlerResponse>
) {
    const orgId = // TODO how do we know which org we are checking?
    const posts = await prisma.post.findMany({
        where: {
            org_id: orgId,
        }
    })
    return res.status(200).json(posts)
}
```

You might notice that the **Post** being imported from **@prisma/client** is our Post type and includes post_id, org_id, user_id, and text.

But there’s a few problems, first, how do we know which tenant/organization we should fetch posts for? And furthermore, how do we know if the person making API requests is in that tenant?

Earlier, we passed in two pieces of information from our frontend:

- An org_id as a query parameter
- An access token that is **verifiable** by our backend

PropelAuth, in addition to providing frontend libraries, also provides backend libraries to verify these tokens. We’ll follow the [getting started guide for Next.js API routes](https://docs.propelauth.com/getting-started/backend-integration/nextjs/?ref=propelauth.mymidnight.blog), which has us use the **@propelauth/express** library:

```shell
yarn add @propelauth/express
```

Set up our backend by calling **initAuth** with the parameters given to us in our dashboard:

```tsx
import {initAuth} from "@propelauth/express";

const propelauth = initAuth({
    authUrl: "YOUR_AUTH_URL",
    apiKey: "YOUR_API_KEY",
    manualTokenVerificationMetadata: {
        verifierKey: "YOUR_VERIFER_KEY",
        issuer: "YOUR_ISSUER_URL",
    }
})
export default propelauth
```

Finally, we’ll call [**requireOrgMember**](https://docs.propelauth.com/getting-started/backend-integration/nextjs/?ref=propelauth.mymidnight.blog#requireorgmember) **,** which will make sure that a valid access token was provided AND that the user is in a specified organization. Putting this all together, our getHandler should look like:

```tsx
import {Post} from '@prisma/client'
import {requireOrgMember, runMiddleware} from "../../lib/propelauth"

export type GetHandlerResponse = Post[]

const requireUserInOrg = requireOrgMember({
    // Tell our middleware that the org_id comes from `req.query`
    orgIdExtractor: (req) => req.query.org_id,
})

async function getHandler(
    req: NextApiRequest,
    res: NextApiResponse<GetHandlerResponse>
) {
    // Make sure the user is in the specified organization, or else return an error
    await runMiddleware(req, res, requireUserInOrg)
    const posts = await prisma.post.findMany({
        where: {
            org_id: req.org.orgId,
        }
    })
    return res.status(200).json(posts)
}
```

We’ll respond to **POST** requests by creating a new post and using the same function:

```tsx
export type PostHandlerResponse = {
    post_id: string,
}

async function postHandler(
    req: NextApiRequest,
    res: NextApiResponse<PostHandlerResponse>
) {
    // Make sure the user is in the specified organization, or else return an error
    await runMiddleware(req, res, requireUserInOrg)

const post = await prisma.post.create({
        data: {
            org_id: req.org.orgId,    // Not only is the org set, but
            user_id: req.user.userId, // the user is also set by requireOrgMember
            text: req.body.text,
        }
    })
    res.status(201).json({"post_id": post.post_id})
}
```

While you are writing this function, you’ll probably notice one of Prisma’s best features—you get both autocomplete and type safety here.

And that’s all! We now have everything we need to test our product.

### Testing

Let’s first test that we cannot view other tenant’s data. If we head over to any arbitrary tenant’s posts page, we’ll see the “Not found” message.

But, like we said before, this is just a frontend check. Let’s use cURL to make sure we cannot make requests directly to our backend:

```shell
$ curl -v -H "Content-Type: application/json"
       -d '{"text": "Hello, tenant I am not a member of!"}'
       http://localhost:3000/api/post\?org_id\=93cae2f3-f9a0-429f-84f9-b5dff4004c65
...
< HTTP/1.1 401 Unauthorized
...
```

As expected, we get a 401 because we didn’t specify an access token signifying we are a valid user. If we do pass in a valid access token, but still pass in an **org_id** that we are not in, we’ll get a 403, signifying that we don’t have permission to view that organizations data.

Finally, we can create an organization in our hosted UIs that PropelAuth provides:

Make a post and verify that we can view it:

### Summary

We’ve built out most of the building blocks we need for a multi-tenant B2B application. Our users can signup, create tenants, and manage the tenant themselves. Our backend is powered by the highly scalable Next.js API routes. And we have both DB migrations and type safety between our application and the database thanks to Prisma.
