# Tutorial: Using Cerbos with FusionAuth

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.

### 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 FustionAuth 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 bee handled by the `docker compose` setup.

The only manual steps required are creating the application. To do this, open up [`http://localhost:9011`](http://localhost:9011/) and complete the setup wizard, then:

1. Create an Application. An Application is something that a user can log into. This is the application we are building or that we are migrating to use FusionAuth.

2. 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.

3. Next, we’ll click the green plus button (the add button) at the top of the page:

4. On the Application form, we’ll need to provide a name for our Application and a couple of items on the OAuth tab. Most of the defaults will work, but we also need to provide these items:
   - An authorized redirect URL: `http://localhost:8080/auth/callback`
   - Optionally, specify a valid Logout URL.
   - Ensure that the Authorization Code grant is selected in the Enabled Grants.

5. Add the roles that will be used by our policies: `user`, `editor` (admin should already exist).

### Configure Node App

Add the Client ID and Client Secret from FusionAuth into the top of `app/index.js` (line 12 & 13).

### Test the app

Now navigate 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`. These roles are authorized as follows:

| 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 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 required from the data store.

7. Call the Cerbos PDP with the principal, resource and action to check authorization.

```javascript
   const allowed = await cerbos.check({
        principal: { //pass in the Okta user ID and groups
          id: req.userContext.userinfo.sub,
          roles: req.userContext.userinfo.groups,
        },
        resource: {
          kind: "contact",
          instances: {
            //a map of the resource(s) being accessed
            [contact.id]: {
              attr: contact,
            },
          },
        },
        actions: ["read"], //the list of actions being performed
    });

if (!allowed.isAuthorized(contact.id, "read")) {
        return res.status(403).json({ error: "Unauthorized" });
    }
   ```

Implementation at this stage will depend on your business requirements.
