Protecting Content
In this step, we'll be implementing a basic authProvider
with check
method to validate the user's authentication status, allowing us to protect our content from unauthenticated users.
Refine can work with any authentication solution with its easy to implement authProvider
interface. We'll set up an implementation for our fake REST API, which also provides a simple authentication endpoints.
To learn more about the supported auth providers, refer to the Supported Authentication Providers section in the Authentication guide.
Creating an Auth Provider
We'll be implementing each method one-by-one, ensuring thorough coverage of all details.
First, we'll create a src/providers/auth-provider.ts
file in our project, which will contain all the methods we need to implement for our auth provider.
Then, we'll pass our auth provider to <Refine />
component in src/App.tsx
file with the authProvider
prop.
Update your src/App.tsx
file by adding the following lines:
import { Refine } from "@refinedev/core";
import { dataProvider } from "./providers/data-provider";
import { authProvider } from "./providers/auth-provider";
import { ShowProduct } from "./pages/products/show";
import { EditProduct } from "./pages/products/edit";
import { ListProducts } from "./pages/products/list";
import { CreateProduct } from "./pages/products/create";
export default function App(): JSX.Element {
return (
<Refine
dataProvider={dataProvider}
authProvider={authProvider}
>
{/* <ShowProduct /> */}
{/* <EditProduct /> */}
<ListProducts />
{/* <CreateProduct /> */}
</Refine>
);
}
Implementing the check
Method
The check
method is used by useIsAuthenticated
hook and the <Authenticated />
component to check the user's authentication status. It should return a Promise
which resolves to an object.
If the user is authenticated, the object should contain authenticated: true
property. Otherwise, it should contain authenticated: false
property.
We'll obtain an access token through the login
method from our API and store it inside the local storage. Now let's check if the token exists in the local storage or not.
Update your src/providers/auth-provider.ts
file by adding the following lines:
import { AuthProvider } from "@refinedev/core";
export const authProvider: AuthProvider = {
check: async () => {
// When logging in, we'll obtain an access token from our API and store it in the local storage.
// Now let's check if the token exists in the local storage.
// In the later steps, we'll be implementing the `login` and `logout` methods.
const token = localStorage.getItem("my_access_token");
return { authenticated: Boolean(token) };
},
login: async ({ email, password }) => {
throw new Error("Not implemented");
},
logout: async () => {
throw new Error("Not implemented");
},
onError: async (error) => {
throw new Error("Not implemented");
},
// ...
};
Using the <Authenticated />
Component
After implementing the check
method, we'll be able to use the <Authenticated />
component to protect our content from unauthenticated users.
Let's add the <Authenticated />
component to our src/App.tsx
file and wrap it around our content inside the <Refine />
component.
Update your src/App.tsx
file by adding the following lines:
import { Refine, Authenticated } from "@refinedev/core";
import { dataProvider } from "./providers/data-provider";
import { authProvider } from "./providers/auth-provider";
import { ShowProduct } from "./pages/products/show";
import { EditProduct } from "./pages/products/edit";
import { ListProducts } from "./pages/products/list";
import { CreateProduct } from "./pages/products/create";
export default function App(): JSX.Element {
return (
<Refine dataProvider={dataProvider} authProvider={authProvider}>
<Authenticated key="protected" fallback={<div>Not authenticated</div>}>
{/* <ShowProduct /> */}
{/* <EditProduct /> */}
<ListProducts />
{/* <CreateProduct /> */}
</Authenticated>
</Refine>
);
}
Notice that we've added key
prop to our <Authenticated />
component. This is required for the component to work properly especially when it's used multiple times in the same render tree.
Now you should be able to see the <Authenticated />
component in action. Our content will not be rendered and the fallback
prop will be rendered instead.
You can also use the useIsAuthenticated
hook instead, which the <Authenticated />
component uses under the hood. You can learn more about it in the useIsAuthenticated hook documentation.
In the next step, we'll be implementing the login and logout functionality and make our check
method work properly.
import { useTable, useMany } from "@refinedev/core"; export const ListProducts = () => { const { tableQuery: { data, isLoading }, current, setCurrent, pageCount, sorters, setSorters, } = useTable({ resource: "products", pagination: { current: 1, pageSize: 10 }, sorters: { initial: [{ field: "id", order: "asc" }] }, }); const { data: categories } = useMany({ resource: "categories", ids: data?.data?.map((product) => product.category?.id) ?? [], }); if (isLoading) { return <div>Loading...</div>; } const onPrevious = () => { if (current > 1) { setCurrent(current - 1); } }; const onNext = () => { if (current < pageCount) { setCurrent(current + 1); } }; const onPage = (page: number) => { setCurrent(page); }; const getSorter = (field: string) => { const sorter = sorters?.find((sorter) => sorter.field === field); if (sorter) { return sorter.order; } } const onSort = (field: string) => { const sorter = getSorter(field); setSorters( sorter === "desc" ? [] : [ { field, order: sorter === "asc" ? "desc" : "asc", }, ] ); } const indicator = { asc: "⬆️", desc: "⬇️" }; return ( <div> <h1>Products</h1> <table> <thead> <tr> <th onClick={() => onSort("id")}> ID {indicator[getSorter("id")]} </th> <th onClick={() => onSort("name")}> Name {indicator[getSorter("name")]} </th> <th> Category </th> <th onClick={() => onSort("material")}> Material {indicator[getSorter("material")]} </th> <th onClick={() => onSort("price")}> Price {indicator[getSorter("price")]} </th> </tr> </thead> <tbody> {data?.data?.map((product) => ( <tr key={product.id}> <td>{product.id}</td> <td>{product.name}</td> <td> { categories?.data?.find( (category) => category.id == product.category?.id, )?.title } </td> <td>{product.material}</td> <td>{product.price}</td> </tr> ))} </tbody> </table> <div className="pagination"> <button type="button" onClick={onPrevious}> {"<"} </button> <div> {current - 1 > 0 && <span onClick={() => onPage(current - 1)}>{current - 1}</span>} <span className="current">{current}</span> {current + 1 < pageCount && <span onClick={() => onPage(current + 1)}>{current + 1}</span>} </div> <button type="button" onClick={onNext}> {">"} </button> </div> </div> ); };