Building Secure Private Screens in React
Dalu46
Posted on July 25, 2024
Introduction
Most modern applications require creating private or restricted pages, documents or even features only accessible to authorized users. This could include financial dashboards and collaborative documents to confidential project plans. One thing that is critical in building modern applications is to ensure that only the right users have access to confidential pages in your application.
Traditionally, implementing fine-grained access control in your application requires significant development effort and time. Fortunately, Permit Share-If offers a streamlined solution. By providing pre-built, embeddable UI components for access sharing, Permit Share-If simplifies managing user permissions.
This article will guide you through a step-by-step process of integrating the Permit.io request access element into a React application to enable you to build private screens in your application.
Prerequisite
- Knowledge of JavaScript, React, and a bit of Nodejs
- A Permit.io account # Authentication vs Authorization
Before going forward, it is important to understand the core difference between authentication and authorization, as some people are still unclear about this.
Authentication is concerned with verifying the identity of a user’s credentials, such as username and password, before granting them access. Conversely, authorization determines what a user can access in a system. Authentication is a prerequisite to authorization.
An example of authentication is logging into an application with a username and password using fingerprints or OTPs. An example of authorization is requesting and getting access to become an editor for a document on Google Docs.
What is Permit.io?
Permit.io is a full-stack authorization-as-a-service that simplifies managing authorization for any application, eliminating the need for developers to build complex authorization systems from scratch. It supports multiple authorization models, including RBAC(Role-based access control), ABAC (Article-based access control), and ReBAC (Relationship-based access control). It integrates swiftly with your dev workflow, leveraging APIs, Terraform providers, and SDKs for various languages.
In this article, to proceed in building private screens in a react application, we will use a Permit.io feature called Access Request Element. This Access-Request Element is a frontend component that enables users to request access to resources, pages on your application, or documents that are restricted or require certain permissions. Once the request is sent from your application, the workspace owner or admin receives it and can decide to approve or deny it.
Implementing Access Request Element
As mentioned earlier, the access request element is a frontend component that allows users to send access requests via a dedicated UI embedded into your application. To use the element, you just have to embed it into your frontend application, and then, by clicking the “Request Access” button, users can request access to restricted resources.
Note: It is vital to have a User Management Element in place first to use the Access Request Element. The workspace owner can review submitted access requests in the User Management Element.
To create a User Management element, navigate to the Permit dashboard, and on the sidebar, click on elements, click on C*reate element* under User Management. Choose the roles for each level and click on Create.
Creating the Request Access Element
To create the Request Access Element, navigate to the management element on the new project you just created, click on elements on the sidebar, and under Access Request, click on Create Element.
You’ll be prompted with a dialog box. First, choose the User Management Element that you want to connect to your Access Request Element, in this case, private-screen, then give the element a name and click Create.
You will need to do the same for the User Management element. Navigate to the User Management element and connect the Request Access element.
Generating the iFrame
After successfully creating the Request Access element, the next step is to get the generated iframe code and embed it in your application; in this case, your React application.
On the Permit dashboard, under the Access Request element, you’ll find the new request-access-tut element that we created. Click on Get Code.
NOTE: It is necessary to select the Tennant appropriate for this element to make it work properly. For this tutorial, we will use the default tenant.
The generated iframe will look similar to this:
<iframe
title= “Permit Element Name”
src="<https://embed.permit.io/><ELEMENT_NAME>?envId=<SOME_UNIQUE_ID>&darkMode=false&resourceInstanceKey=<RESOURCE_INSTANCE_KEY>&tenantKey=<TENANT_KEY>"
width= “100%”
height= “100%”
style= “border: none;”
/>
The iframe must be embedded after it is generated. You’ll need to establish a login to do that. I’ll guide you through the login process in the next section.
Creating React App
To test out the newly generated iframe, let’s bootstrap a simple React app like so:
npx create-react-app private screen
Install the required dependency
npm install –-save @permitio/permit-js
We first need to configure a backend route using one of the options provided by permit.io, which is either cookies, headers, bearer tokens, or a frontend-only method. We’d be using the login with cookie method for this tutorial.
Note: It is important to use the same login method on the backend and frontend.
Creating the backend route
I have initialized a backend server using Node.js. In your React application, create a backend folder, and in the backend folder, create a server.js file. Paste the following code into the file, then let’s go over explaining each line of the code:
const { Permit } = require("permitio");
const express = require("express");
const path = require("path");
jav
require("dotenv").config({ path: path.resolve(__dirname, "../.env") });
const app = express();
const port = 8080;
console.log(process.env.PERMIT_KEY_SECRET);
const PERMIT_KEY_SECRET = YOUR_API_KEY; // your api key gotten from the permit dashboard
const USER_KEY = "paul@gmail.com";
const TENANT_ID = YOUR_TENANT_ID; // the tenant for your project, usually 'default'
const permit = new Permit({ token: PERMIT_KEY_SECRET });
app.get("/login_cookie", async (req, res) => {
console.log("login_cookie");
const ticket = await permit.elements.loginAs({
userId: USER_KEY,
tenantId: TENANT_ID,
});
res.status(302).redirect(ticket.redirect_url);
});
app.listen(port, () => {
console.log(`Example app listening at <http://localhost>:${port}`);
});
At the top, I have declared two main variables, which are the PERMIT_KEY_SECRET which is your API key, and TENANT_ID, which is the default tenant. I set the current user as myself but generally, this user will be the one that has been authenticated by your application through an authentication provider.
To get your Permit API key, go to your dashboard, click on Projects, then click on the project and environment (i.e., development) to connect to. Click on the menu icon on the top right of the environment element and click on Copy API Key. Traditionally, you’d need to store the key in an env variable.
After declaring the tenant, the next thing is to initialize Permit, and then define the endpoint /login_cookie using app.get
.
Next, we login the user to Permit using the loginAs
function, which takes two parameters: a userId, and a tenant_key or tenantId.
The route redirects the user to the redirect_url provided in the login ticket using a 302 (Found) status code.
Login the User on the Frontend
In the src of the application, create a Login.js file and paste the following code into the file:
import React, { useState } from "react";
import permit, { LoginMethod } from "@permitio/permit-js";
import { btnStyle } from "./Home";
const Login = () => {
const [isLogin, setIsLogin] = useState(false);
const tenantKey = "default";
const backendUrl = `http://localhost:8080/login_cookie`;
const login = () => {
permit.elements
.login({
loginUrl: backendUrl,
tenant: tenantKey,
loginMethod: LoginMethod.cookie,
})
.then((res: any) => {
console.log("res", res);
setIsLogin(true);
})
.catch((err: any) => {
console.log("err", err);
setIsLogin(false);
});
};
return (
<div>
<button
style={btnStyle}
onClick={() => {
login();
}}
>
login
</button>
<button
style={btnStyle}
onClick={() => {
permit.elements.logout();
}}
>
logout
</button>
{isLogin && <div>Logged in</div>}
</div>
);
};
export default Login;
First, we created an isLogin
state to track when a user is logged in. Then, we use the login method to log in the user to Permit. The login method accepts three parameters: loginUrl, tenant, and a LoginMethod.
- loginUrl: This is the URL you created in the previous stage (backend).
- LoginMethod: The backend login mechanism that you are utilizing.
- Tenant (Optional): The tenant name that the user belongs to
Once the login is successful, we update the i’sLogin “state to true. Finally, we set an onClick handler to enable users to log in and out.
Creating the Access Request and User Management Pages
Next, create a UserManagement.js file and paste the generated User Management iframe code. The User Management element will be used by the admin to approve or deny users access to the restricted page. After that, create a RequestAccess.js file and paste the generated Request Access iframe code into it.
import React from “react”;
const RequestAccess = () => {
// TODO paste your iframe code here
return (
<div style={{ height: "60vh", width: "80%", margin: "0 auto"}}>
<iframe
title= "Permit Element Name"
src="<https://embed.permit.io/><ELEMENT_NAME>?envId=<SOME_UNIQUE_ID>&darkMode=false&resourceInstanceKey=<RESOURCE_INSTANCE_KEY>&tenantKey=<TENANT_KEY>"
width= "100%"
height= "100%"
/>
</div>
);
};
export default RequestAccess;
Now create a Home.js file and paste the following code:
const Home = () => {
const nav = useNavigate();
return (
<div>
<Login />
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<div>
<button
style={btnStyle}
onClick={() => {
nav(`/request-access`);
}}
>
View Private Screen
</button>
</div>
</div>
</div>
);
};
export default Home;
In the Home component, we’ve created a View Private Screen button and set the navigation to the RequestAccess page. Once a user clicks on it, it directs them to first request for access, and when they are granted access, it redirects them to the restricted page.
Now create a PrivatePage.js file
and paste the following code:
import React from “react”;
const PrivatePage = () => {
return (
<div style={{ height: “60vh”, width: “80%”, margin: “0 auto”}}>
<p>This is a Private page. You are only here because you were granted access</p>
</div>
);
};
export default PrivatePage;
Finally, paste the following code into the `App.js` file while I explain below:
import React, { useState, useEffect } from 'react';
import RequestAccess from "./RequestAccess";
import PrivatePage from "./RequestAccess";
import Home from "./components/Home";
function App() {
const [accessGranted, setAccessGranted] = useState(null);
const [message, setMessage] = useState('');
const cookie = '<COOKIE FROM LOGIN>' // Replace with your actual cookie value
const element_id = 'ELEMENTS_CONFIG_ID'
const project_id = '{project_id}' // Replace with your project ID
const env_id = '{env_id}' // Replace with your environment ID
const access_request_id = '{access_request_id}' // Replace with your access request ID
useEffect(() => {
const checkAccess = async () => {
try {
fetch(`https://api.permit.io/v2/facts/${projectId}/${envId}/access_requests/${accessRequestId}`, {
method: 'GET',
headers: {
'cookie': cookie,
'element_id': elementId
}
})
.then(response => response.json())
.then(data => {
console.log('Access request data:', data);
if (data.status === 'approved') {
setAccessGranted(true);
} else {
setAccessGranted(false);
}
})
.catch(error => {
console.error('Error fetching access request:', error);
setAccessGranted(false)
});
checkAccess();
}, []);
return (
<div className="App">
<BrowserRouter>
<Home />
<Routes>
<Route path="/" element={<div />} />
{accessGranted ? ( <Route path="/private-page" element={<PrivatPage />} /> ) : ( <Route path="/embed" element={<RequestAccess />} /> )}
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
First, we have set up a state to track when the user has been granted access to the restricted page. Next we use the API provided by Permit to check the status of a request if it is approved or not. This API takes four parameters:
- cookie: This is the cookie you have extracted from logging in a user into Permit
- element_id = The element_id is the text after the domain on the iframe’s
src
- project_id: This is the id of your project. Follow this link to get your project id
- env_id = Env id. Follow this link to get your env id
- access_request_id: The id of the access request you want to check. Follow this link to get an access_request id.
Once this request is successful, we check to see if the status from the data is “approved” or not and set the accessGranted
state based on the status. Then we check in the UI code to know if a user has been granted access and show them either the Private Page if they have been granted access or the Request Access element if they haven’t.
An image of the element_id
Running the Application
Spin up the local server for the frontend application using npm start
and then spin up the server by navigating to the server folder in your terminal and running the command node server.js
When the user clicks the View Private Screen
button, the Request Access elements pop up. Once they request access, it is sent to an admin for approval.
After sending the access request with a viewer account, I logged in with an admin account. I navigated to the User Management page just to show how easy it is to accept or reject the viewer account request to access the private screen that I’ve created.
Admin (user management element)
Now, as an admin, I can approve or deny access to my restricted page. Once the admin approves, then the user who requested access gets access the the restricted/private page.
Conclusion
In this article, we learned how to create private/restricted screens in React leveraging Permit.io’s Access Request element which saves the time and effort that would have been spent on trying to implement this feature from scratch.
Resources:
- Learn more on Access Request
- Learn more about User Management
- Learn more about Permit
CTA:
Ready to revolutionize your application’s access control? Check out Permit Share-If on Product Hunt and learn how it can transform your development process.
Posted on July 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.