Postgraphile is a great tool for making instant GraphQL from a PostgreSQL database. When I started working with Postgraphile, its authorization part felt a bit different compared to the REST based backends which I had worked with before. Here I will share some differences that I noted.
First, let's see Authentication vs Authorization.
Authentication is determining whether a user is logged in or not. Authorization is then deciding what the users has permission to do or see.
Comparing the implementation of a blog application using Postgraphile vs REST
Suppose we have to build a blog application with the below schema.
Features of the blog application.
-
Display published blogs with is_published = true to all users.
-
Display unpublished blogs with is_published = false to its creator only.
REST Implementation
The REST implementation with JavaScript and sequelize can be like below.
The client requests the blogs using an endpoint, it also attaches the access token received from the authentication service.
1const getBlogs = () => 2 requestData({ 3 endpoint: `/api/blogs`, 4 accessToken: "***", 5 });
The backend code in the server receives the request, finds the current logged in user from the access token, and requests the data based on the current logged in user from the database.
1const userEmail = findEmail(accessToken); 2const blogs = await models.Blogs.findAll({ 3 where: { [Op.or]: [{ creatorEmail: userEmail }, { isPublished: true }] }, 4}); 5 6res.send(blogs);
Here, the backend code finds the user’s email from the access token, then requests the database to give the list of blogs that have creatorEmail matching to the current user's email or the field isPublished is true.
The database will return whatever data the server requests.
Similarly, for creating, editing, and deleting blogs, we can have different end-points to handle the authorization logic in the backend code.
Postgraphile Implementation
The postgraphile implementation can be like below.
The client requests the blogs using a GraphQL query. It also attaches the access token received from the authentication service.
1const data = requestQuery({ 2 query: "allBlogs { 3 nodes { 4 content 5 creatorEmail 6 visiblityType 7 } 8 }" 9 accessToken: '***' 10})
In the server, we configure Postgraphile to pass the user information to the database.
1export postgraphile(DATABASE_URL, schemaName, { 2 pgSettings: (req) => { 3 const userEmail = findEmail(accessToken); 4 return({ 5 'current_user_email': userEmail 6 }) 7 } 8})
We can pass a function as Postgraphile’s pg Settings property, whose return value will be accessible from the connected Postgres database by calling the current_setting function.
In the database, the row-level security policies can be defined to control the data access.
Row-level security policies are basically just SQL that either evaluates to true or false. If a policy is created and enabled for a table, that policy will be checked before doing an operation on the table.
1create policy blogs_policy_select 2on public.blogs for select to users 3USING ( 4 isPublished OR 5 creator_email = current_setting('current_user_email') 6); 7ALTER TABLE blogs ENABLE ROW LEVEL SECURITY;
Here the policy named blogs_policy_select will be checked before selecting a row in the table public.blogs. A row will be selected only if the isPublished field is true or creator_email matches with the current user's email.
Similarly, for creating, editing, and deleting blogs, we can have row level security policies for INSERT, UPDATE, and DELETE operations on the table.
Conclusion
The REST implementation does the authorization on the server level but the Postgraphile does it on the database level. Each implementation has its own advantages and disadvantages, which is a topic for another day.