# Tutorial: Using Cerbos with FusionAuth

This documentation is for a previous version of Cerbos. Choose 0.53.0 from the version picker at the top right or navigate to [https://docs.cerbos.dev](https://docs.cerbos.dev/) for the latest version.

An example stack of integrating [Cerbos](/content/site-root.html) with an [Express](https://expressjs.com/) server using [FusionAuth](https://fusionauth.com/) for authentication.

This example is based off the [FusionAuth/fusionauth-example-node](https://github.com/fusionauth/fusionauth-example-node) repo.

Using Cerbos with FusionAuth - YouTube

[Using Cerbos with FusionAuth](https://www.youtube.com/watch?v=Uf1jmDn4YSE)

## Dependencies

- docker-compose

## Getting started

1. Clone the repo

```bash
   git clone git@github.com:cerbos/express-fusionauth-cerbos.git
   ```

### Start Stack

Start up the stack `docker compose up` - this will take some time to pull down all the images and launch them, but once started the following services will be running.

- FusionAuth [`http://localhost:9011`](http://localhost:9011/)
- Cerbos [`http://localhost:3592/`](http://localhost:3592/)
- Node App [`http://localhost:8080/`](http://localhost:8080/)
- Postgres DB for FusionAuth on port `5432`

### Configure FusionAuth

This example is based off the [FusionAuth 5 Minute Guide](https://fusionauth.io/docs/v1/tech/5-minute-setup-guide/) - and most of the steps have been handled by the `docker compose` setup.

Once we arrive in the FusionAuth admin UI, we need to create an Application. This is the application our users will log into. We’ll click the Application menu option on the left side of the page or the Setup button in the box at the top of the page.

This will take us to the listing page for Applications. Next, we’ll click the green plus button (the add button) at the top of the page:

On the Application form, we’ll need to provide a name for our Application and a couple of items on the OAuth tab. We’ll start with a simple setup that allows existing users to log into your application.

We need to ensure the Authorization Code grant is selected in the Enabled Grants.

Next we need to add the roles that will be used by our policies. Back on the application listing page press the 'Manage Roles' button next to our application and add roles for `user` and `editor` (admin should already exist). These roles will be passed back with the user information to our application.

Once configured, we can copy the Client ID and Client Secret and move to the next step.

### Configure Node App

Now that our application has been created, we need to add the Client ID and Client Secret from FusionAuth into the top of `app/index.js` (line 12 & 13).

### Test the app

Now that everything is wired up you should be able to go to [`http://localhost:8080`](http://localhost:8080/) and press the login link to authenticate with your FusionAuth account.

## Policies

This example has a simple CRUD policy in place for a resource kind of `contact` - like a CRM system would have. The policy file can be found in the `cerbos/policies` folder [here](https://github.com/cerbos/express-fusionauth-cerbos/blob/main/cerbos/policies/contact.yaml).

The policy expects one of two roles to be set on the principal - `admin` and `user`.

| Action | User | Admin |
| --- | --- | --- |
| list | Y | Y |
| read | Y | Y |
| create | Y | Y |
| update | If owner | Y |
| delete | If owner | Y |

## Request Flow

1. User accesses the application and clicks `Login`
2. User is directed to the FusionAuth UI and authenticates
3. A token is returned back in the redirect URL to the application
4. That token is then exchanged for the user profile information
5. The user profile from FusionAuth is stored (user Id, email, roles, etc.)
6. Any requests to the `/contacts` endpoints fetch the data from the datastore
7. Call the Cerbos PDP with the principal, resource and action to check the authorization.

```javascript
   const allowed = await cerbos.check({
        principal: {
          id: req.userContext.userinfo.sub,
          roles: req.userContext.userinfo.groups,
        },
        resource: {
          kind: "contact",
          instances: {
            [contact.id]: {
              attr: contact,
            },
          },
        },
        actions: ["read"],
    });
   ```
   
   if (!allowed.isAuthorized(contact.id, "read")) {
   return res.status(403).json({ error: "Unauthorized" });
   }
   ```
