Skip to main content

Integrate Within a Framework

note
OpenFGA is an open source Fine-Grained Authorization solution based on Google's Zanzibar. We welcome community contribution to this project.

This section will illustrate how to integrate OpenFGA within a framework environment, such as Fastify or Fiber.

Before You Start

  1. Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_HOST and, if needed, FGA_API_TOKEN.
  2. You have installed the OpenFGA SDK.
  3. You have configured the authorization model and updated the relationship tuples.
  4. You know how to perform a Check.
  5. You have loaded FGA_API_HOST and FGA_STORE_ID as environment variables.

Step By Step

Assume that you want to have a web service for documents using one of the frameworks mentioned above. The service will authenticate users via JWT tokens, which contain the user ID.

Note

The reader should set up their own login method based on their OpenID connect provider's documentation.

Assume that you want to provide a route GET /read/{document} to return documents depending on whether the authenticated user has access to it.

01. Install And Setup Framework

The first step is to install the framework.

For the context of this example, we will use the Fastify framework. For that we need to install the following packages:

  • the fastify package that provides the framework itself
  • the fastify-plugin package that allows integrating plugins with Fastify
  • the fastify-jwt package for processing JWT tokens

Using npm:

npm install fastify fastify-plugin fastify-jwt

Using yarn:

yarn add fastify fastify-plugin fastify-jwt

Next, we setup the web service with the GET /read/{document} route in file app.js.

// Require the framework and instantiate it
const fastify = require('fastify')({ logger: true });

// Declare the route
fastify.get('/read/:document', async (request, reply) => {
return { read: request.params.document };
});

// Run the server
const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();

02. Authenticate And Get User ID

Before we can call OpenFGA to protect the /read/{document} route, we need to validate the user's JWT.

The fastify-jwt package allows validation of JWT tokens, as well as providing access to the user's identity.

In jwt-authenticate.js:

const fp = require('fastify-plugin');

module.exports = fp(async function (fastify, opts) {
fastify.register(require('fastify-jwt'), {
secret: {
private: readFileSync(`${path.join(__dirname, 'certs')}/private.key`, 'utf8'),
public: readFileSync(`${path.join(__dirname, 'certs')}/public.key`, 'utf8'),
},
sign: { algorithm: 'RS256' },
});

fastify.decorate('authenticate', async function (request, reply) {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
});

Then, use the preValidation hook of a route to protect it and access the user information inside the JWT:

In route-read.js:

module.exports = async function (fastify, opts) {
fastify.get(
'/read/:document',
{
preValidation: [fastify.authenticate],
},
async function (request, reply) {
// the user's id is in request.user
return { read: request.params.document };
},
);
};

Finally, update app.js to register the newly added hooks.

const fastify = require('fastify')({ logger: true });
const jwt-authenticate = require('./jwt-authenticate');
const routeread = require('./route-read');

fastify.register(jwt-authenticate);
fastify.register(routeread);

// Run the server!
const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
}
start();

03. Integrate The OpenFGA Check API Into The Service

First, we will create a decorator preauthorize to parse the incoming HTTP method as well as name of the document, and set the appropriate relation and object that we will call Check on.

In preauthorize.js:

const fp = require('fastify-plugin');

module.exports = fp(async function (fastify, opts) {
fastify.decorate('preauthorize', async function (request, reply) {
try {
switch (request.method) {
case 'GET':
request.relation = 'reader';
break;
case 'POST':
request.relation = 'writer';
break;
case 'DELETE':
default:
request.relation = 'owner';
break;
}
request.object = `document:${request.params.document}`;
} catch (err) {
reply.send(err);
}
});
});

Next, we will create a decorator called authorize. This decorator will invoke the Check API to see if the user has a relationship with the specified document.

In authorize.js:

const fp = require('fastify-plugin');
const { OpenFgaApi } = require('@openfga/sdk'); // OR import { OpenFgaApi } from '@openfga/sdk';

module.exports = fp(async function (fastify, opts) {
fastify.decorate('authorize', async function (request, reply) {
try {
// configure the openfga api client
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Optional. Can be "http" or "https". Defaults to "https"
apiHost: process.env.FGA_API_HOST,
storeId: process.env.FGA_STORE_ID,
});
const { allowed } = await fgaClient.check({
tuple_key: {
user: request.user,
relation: request.relation,
object: request.object,
},
});
if (!allowed) {
reply.code(401).send(`Not authenticated`);
}
} catch (err) {
reply.send(err);
}
});
});

We can now update the GET /read/{document} route to check for user permissions.

In route-read.js:

module.exports = async function (fastify, opts) {
fastify.get(
'/read/:document',
{
preValidation: [fastify.authenticate, fastify.preauthorize, fastify.authorize],
},
async function (request, reply) {
// the user's id is in request.user
return { read: request.params.document };
},
);
};

Finally, we will register the new hooks in app.js:

const fastify = require('fastify')({ logger: true });
const jwt-authenticate = require('./jwt-authenticate');
const preauthorize = require('./preauthorize');
const authorize = require('./authorize');
const routeread = require('./route-read');

fastify.register(jwt-authenticate);
fastify.register(preauthorize);
fastify.register(authorize);
fastify.register(routeread);

const start = async () => {
try {
await fastify.listen(3000);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
}
start();
Entitlements

Modeling Entitlements for a System in OpenFGA.

IoT

Modeling Fine Grained Authorization for an IoT Security Camera System with OpenFGA.

Slack

Modeling Authorization for Slack with OpenFGA.