Skip to main content
Version: 3.xx.xx

2. Create Auth Provider From Scratch

This section will show you how to create an auth provider from scratch. We'll use mock data to be able to focus on the auth provider methods. When you understand the logic of auth provider, you can easly integrate third-party authentication services or your own custom auth provider which includes many possible strategies like JWT, OAuth, etc.

Create Mock Auth Provider

  1. Create a new file named authProvider.ts in src folder and add the following code:

    src/authProvider.ts
    import { AuthProvider } from "@pankod/refine-core";

    const authProvider: AuthProvider = {
    login: () => Promise.resolve(),
    checkAuth: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    };

    export default authProvider;

    We created a mock auth provider. It has all the required methods. But, they don't do anything. We'll add the logic to these methods in the next.

  2. Now, we need to pass the authProvider to the <Refine/> component. Open App.tsx file and add related code:

    src/App.tsx
    ...
    import authProvider from "./authProvider";

    <Refine
    ...
    authProvider={authProvider}
    />

    The authProvider is not required for the <Refine/> component. If you don't pass it, your app will work without authentication. But, you won't be able to use the auth hooks.


We created a mock auth provider and passed it to the <Refine/> component. Now, we'll add the logic to the auth provider methods.

Required Methods

login

login method is used to authenticate users. It expects to return a Promise.

  • If the Promise resolves, the user is authenticated and pages that require authentication will be accessible.

  • If the Promise rejects, the user is not authenticated and stays on the login page.

We'll use mock data to authenticate users. So, we'll create a mock user list and check if the user exists in the list. If the user exists, we'll save the user data to the local storage and resolve the Promise. Otherwise, we'll reject the Promise.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const mockUsers = [{ email: "john@mail.com" }, { email: "jane@mail.com" }];

const authProvider: AuthProvider = {
login: ({ email, password }) => {
// Suppose we actually send a request to the back end here.
const user = mockUsers.find((item) => item.email === email);

if (user) {
localStorage.setItem("auth", JSON.stringify(user));
return Promise.resolve();
}

return Promise.reject();
},
...
};

Invoking the useLogin hook's mutation will call the login method, passing in the mutation's parameters as arguments. This means the parameters for the useLogin hook's mutation must match the parameters of the login method.

Refer to the useLogin documentation for more information

For example, if we call the useLogin hook's mutation like this:

import { useLogin } from "@pankod/refine-core";

const { mutate } = useLogin();

mutate({ email: "john@mail.com", password: "123456"}}

The login method will get the mutation's parameters as arguments.

At this point, we can authenticate users. But, we can't check if the user is authenticated or not when the user refreshes the page or navigates to another page. We'll add the logic to the checkAuth method to solve this problem.


Can I pass any parameters to the login method?

Yes, you can pass any parameters to the login method. useLogin hook's mutation will pass the mutation's parameters to the login method without any type constraints.

const { mutate } = useLogin<{
username: string;
password: string;
foo: string;
remember: boolean;
}>();
How can I redirect the user to a specific page after login?

By default, the user will be redirected to the / route after login. If you want to redirect the user to a specific page, you can resolve the login method's Promise with the path of the page.

const authProvider: AuthProvider = {
...
login: () => {
...
return Promise.resolve("/custom-page");
}
}

Also, you can use the useLogin hook's for this purpose.

const { mutate } = useLogin();

mutate({ redirectPath: "/custom-page" });

Then, you can use the redirectPath parameter in the login method to redirect the user to the specific page.

const authProvider: AuthProvider = {
...
login: ({ redirectPath }) => {
...
return Promise.resolve(redirectPath);
}
}

If you don't want to redirect the user to anywhere, you can resolve the login method's Promise with false.

const authProvider: AuthProvider = {
...
login: () => {
...
return Promise.resolve(false);
}
}
How can I customize the error message?

refine automatically displays an error notification when the login method rejects the Promise. If you want to customize the error message, you can reject the Promise with an object that has name and message properties.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
login: ({ email, password }) => {
...
return Promise.reject({
name: "Login Failed!",
message: "The email or password that you've entered doesn't match any account.",
});
},
...
};

checkAuth

checkAuth method is used to check if the user is authenticated. Internally, it is called when the user navigates to a page that requires authentication.

checkAuth method expects to return a Promise.

  • If the Promise resolves, the user is authenticated and pages that require authentication will be accessible.

  • If the Promise rejects, the user is not authenticated and pages that require authentication will not be accessible and by default, the user will be redirected to the /login page.

In the login method, we've saved the user data to the local storage when the user logs in. So, we'll check if the user data exists in the local storage to determine if the user is authenticated.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
...
checkAuth: () => {
const user = localStorage.getItem("auth");

if (user) {
return Promise.resolve();
}

return Promise.reject();
},
...
};

Invoking the useAuthenticated hook will call the checkAuth method. If checkAuth method resolves a data, it will be available in the useAuthenticated hook's data property.

Refer to the useAuthenticated documentation for more information

import { useAuthenticated } from "@pankod/refine-core";

const { data, isSuccess, isLoading, isError, refetch } = useAuthenticated();
tip

The <Authenticated> component makes use of the useAuthenticated hook. It allows you to render components only if the user is authenticated.

Refer to the <Authenticated> documentation for more information


How can I redirect the user if the user is not authenticated?

By default, the user will be redirected to /login if the checkAuth method rejects the Promise. If you want to redirect the user to a specific page, you can reject the Promise with an object that has redirectPath property.

const authProvider: AuthProvider = {
...
checkAuth: () => {
...
return Promise.reject({
redirectPath: "/custom-page",
});
}
}

logout

logout method is used to log out users. It expects to return a Promise.

  • If the Promise resolves, the user is logged out and pages that require authentication will not be accessible and by default, the user will be redirected to the /login page.

  • If the Promise rejects, the user is not logged out and stays on the page.

In the login method, we've saved the user data to the local storage when the user logs in. So, we'll remove the user data from the local storage when the user logs out.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
...
logout: () => {
localStorage.removeItem("auth");
return Promise.resolve();
},
...
};

Invoking the useLogout hook's mutation will call the logout method. If you need to pass any parameters to the logout method, you can use the useLogout hook's mutation.

Refer to the useLogout documentation for more information

For example, if we call the useLogout hook's mutation like this:

import { useLogout } from "@pankod/refine-core";

const { mutate } = useLogout();

mutate({ id: "1" });

The logout method will get the mutation's parameters as an argument.


Can I pass any parameters to the logout method?

Yes, you can pass any parameters to the logout method. useLogout hook's mutation will pass the mutation's parameters to the login method without any type constraints.

const { mutate } = useLogin<{
id: string;
name: string;
}>();
How can I redirect the user to a specific page after logout?

By default, the user will be redirected to the /login route after logout. If you want to redirect the user to a specific page, you can resolve the logout method's Promise with the path of the page.

const authProvider: AuthProvider = {
...
logout: () => {
...
return Promise.resolve("/custom-page");
}
}

Also, you can use the useLogout hook's for this purpose.

const { mutate } = useLogout();

mutate({ redirectPath: "/custom-page" });

Then, you can use the redirectPath parameter in the logout method to redirect the user to the specific page.

const authProvider: AuthProvider = {
...
logout: ({ redirectPath }) => {
...
return Promise.resolve(redirectPath);
}
}

If you don't want to redirect the user to anywhere, you can resolve the logout method's Promise with false.


const authProvider: AuthProvider = {
...
logout: () => {
...
return Promise.resolve(false);
}
}
How can I customize the error message?

refine automatically displays an error notification when the logout method rejects the Promise. If you want to customize the error message, you can reject the Promise with an object that has name and message properties.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
logout: () => {
...
return Promise.reject({
name: "Logout Failed!",
message: "Something went wrong.",
});
},
...
};

checkError

checkError method is called when you get an error response from the API. You can create your own business logic to handle the error such as refreshing the token, logging out the user, etc.

checkError method expects to return a Promise.

  • If the Promise resolves, the user is not logged out and stays on the page.

  • If the Promise rejects, the logout method is called to log out the user and by default, the user is redirected to the /login route.

We'll use the checkError method to log out the user if the API returns a 401 or 403 error.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
...
checkError: (error) => {
if (error.status === 401 || error.status === 403) {
return Promise.reject();
}

return Promise.resolve();
},
...
};

Invoking the useCheckError hook's mutation will call the checkError method, passing in the mutation's parameters as arguments.

Refer to the useCheckError documentation for more information

For example, if you want to check the error of a fetch request, you can use the useCheckError hook's mutation like this:

import { useCheckError } from "@pankod/refine-core";

const { mutate } = useCheckError();

fetch("http://example.com/payment")
.then(() => console.log("Success"))
.catch((error) => mutate(error));

How can I redirect the user to a specific page after logout?

By default, the user will be redirected to the /login route after rejecting the checkError method's Promise. If you want to redirect the user to a specific page, you can reject the Promise with an object that has redirectPath property.

const authProvider: AuthProvider = {
...
checkError: (error) => {
if (error.status === 401 || error.status === 403) {
return Promise.reject({
redirectPath: "/custom-page",
});
}

return Promise.resolve();
},
...
}

Optional Methods

getPermissions

getPermissions method is used to get the user's permissions. It expects to return a Promise.

  • If the Promise resolves with data, the user's permissions will be available in the usePermissions hook's data property.

  • If the Promise rejects, the user's permissions will not be available and usePermissions hook throw an error.

We'll use the getPermissions method to get the user's permissions from the localStorage.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const mockUsers = [
{ email: "john@mail.com", roles: ["admin"] },
{ email: "jane@mail.com", roles: ["editor"] },
];

const authProvider: AuthProvider = {
...
getPermissions: () => {
const user = localStorage.getItem("auth");

if (user) {
const { roles } = JSON.parse(user);

return Promise.resolve(roles);
}

return Promise.reject();
},
...
};

Invoking the usePermissions hook will call the getPermissions method. If getPermissions method resolves a data, it will be available in the usePermissions hook's data property.

Refer to the usePermissions documentation for more information

For example, if you want to check if the user has a specific permission, you can use the usePermissions hook like this:

import { usePermissions } from "@pankod/refine-core";

const { data } = usePermissions();

if (data?.includes("admin")) {
console.log("User has admin permissions");
}

info

usePermissions hook can be used for simply authorization purposes. If you need more complex authorization logic, we recommend using the access control provider to handle the authorization logic.

Refer to the accessControlProvider documentation for more information

getUserIdentity

getUserIdentity method is used to get the user's identity. It expects to return a Promise.

  • If the Promise resolves with a data, the user's identity will be available in the useGetIdentity hook's data property.

  • If the Promise rejects, the user's identity will not be available and useGetIdentity hook throw an error.

We'll get the user's identity from the local storage and resolve the Promise.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const mockUsers = [
{ email: "john@mail.com", roles: ["admin"] },
{ email: "jane@mail.com", roles: ["editor"] },
]

const authProvider: AuthProvider = {
...
getUserIdentity: () => {
const user = localStorage.getItem("auth");

if (user) {
const { email, roles } = JSON.parse(user);

return Promise.resolve({ email, roles });
}

return Promise.reject();
},
...
};

Invoking the useGetIdentity hook will call the getUserIdentity method. If getUserIdentity method resolves a data, it will be available in the useGetIdentity hook's data property.

Refer to the useGetIdentity documentation for more information

For example, if you want to get the user's email, you can use the useGetIdentity hook like this:

import { useGetIdentity } from "@pankod/refine-core";

const { data } = useGetIdentity();

if (data) {
console.log(data.email);
}
info

Depending on the UI framework you use, if you resolve name and avatar properties in the getUserIdentity method, the user's name and avatar will be shown in the header in the default layout.

const authProvider: AuthProvider = {
...
getUserIdentity: () => {
const user = localStorage.getItem("auth");

if (user) {
const { email, roles } = JSON.parse(user);

return Promise.resolve({
email,
roles,
name: "John Doe",
avatar: "https://i.pravatar.cc/300",
});
}

return Promise.reject();
},
...
};

register

register method is used to register a new user. It is similar to the login method. It expects to return a Promise.

  • If the Promise resolves, by default, the user will be redirected to the / page.

  • If the Promise rejects, the useRegister hook will throw an error.

We'll register a new user and resolve the Promise.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const mockUsers = [{ email: "john@mail.com" }, { email: "jane@mail.com" }];

const authProvider: AuthProvider = {
...
register: ({ email }) => {
const user = mockUsers.find((user) => user.email === email);

if (user) {
return Promise.reject();
}

mockUsers.push({ email });

return Promise.resolve();
},
...
};

Invoking the useRegister hook's mutation will call the register method, passing in the mutation's parameters as arguments.

Refer to the useRegister documentation for more information

For example, if you want to register a new user, you can use the useRegister hook like this:

import { useRegister } from "@pankod/refine-core";

const { mutate } = useRegister();

const handleRegister = (values) => {
mutate(values);
};

The register method will get the mutation's parameters as arguments.


Can I pass any parameters to the register method?

Yes, you can pass any parameters to the register method. useRegister hook's mutation will pass the mutation's parameters to the register method without any type constraints.

const { mutate } = useRegister<{
username: string;
email: string;
password: string;
confirmPassword: string;
remember: boolean;
}>();
How can I redirect the user to a specific page after registration?

By default, the user will be redirected to the / route after registration. If you want to redirect the user to a specific page, you can resolve the register method's Promise with the path of the page.

const authProvider: AuthProvider = {
...
register: () => {
...
return Promise.resolve("/custom-page");
}
}

Also, you can use the useRegister hook's for this purpose.

const { mutate } = useRegister();

mutate({ redirectPath: "/custom-page" });

Then, you can use the redirectPath parameter in the register method to redirect the user to the specific page.

const authProvider: AuthProvider = {
...
register: ({ redirectPath }) => {
...
return Promise.resolve(redirectPath);
}
}

If you don't want to redirect the user to anywhere, you can resolve the register method's Promise with false.

const authProvider: AuthProvider = {
...
register: () => {
...
return Promise.resolve(false);
}
}
How can I customize the error message?

refine automatically displays an error notification when the register method rejects the Promise. If you want to customize the error message, you can reject the Promise with an object that has name and message properties.

src/authProvider.ts

const authProvider: AuthProvider = {
...
register: () => {
...
return Promise.reject({
name: "Error",
message: "Something went wrong!",
});
}
}

forgotPassword

forgotPassword method is used to send a password reset link to the user's email address. It expects to return a Promise.

We'll show how to send a password reset link to the user's email address and resolve the Promise.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
...
forgotPassword: ({ email }) => {
// send password reset link to the user's email address here
// if request is successful, resolve the Promise, otherwise reject it
return Promise.resolve();
},
...
};

Invoking the useForgotPassword hook's mutation will call the forgotPassword method, passing in the mutation's parameters as arguments.

Refer to the useForgotPassword documentation for more information

For example, if you want to send a password reset link to the user's email address, you can use the useForgotPassword hook like this:

import { useForgotPassword } from "@pankod/refine-core";

const { mutate } = useForgotPassword();

const handleForgotPassword = (values) => {
mutate(values);
};

The forgotPassword method will get the mutation's parameters as arguments.


Can I pass any parameters to the forgotPassword method?

Yes, you can pass any parameters to the forgotPassword method. useForgotPassword hook's mutation will pass the mutation's parameters to the forgotPassword method without any type constraints.

const { mutate } = useForgotPassword<{
email: string;
}>();
How can I redirect the user to a specific page after sending the password reset link?

By default, the user won't be redirected to anywhere after sending the password reset link. If you want to redirect the user to a specific page, you can resolve the forgotPassword method's Promise with the path of the page.

const authProvider: AuthProvider = {
...
forgotPassword: () => {
...
return Promise.resolve("/custom-page");
}
}

Also, you can use the useForgotPassword hook's for this purpose.

const { mutate } = useForgotPassword();

useForgotPassword({ redirectPath: "/custom-page" });

Then, you can use the redirectPath parameter in the forgotPassword method to redirect the user to the specific page.

const authProvider: AuthProvider = {
...
forgotPassword: ({ redirectPath }) => {
...
return Promise.resolve(redirectPath);
}
}
How can I customize the error message?

refine automatically displays an error notification when the forgotPassword method rejects the Promise. If you want to customize the error message, you can reject the Promise with an object that has name and message properties.

src/authProvider.ts
const authProvider: AuthProvider = {
...
forgotPassword: () => {
...
return Promise.reject({
name: "Error",
message: "Something went wrong!",
});
}
}

updatePassword

updatePassword method is used to update the user's password. It expects to return a Promise.

We'll show how to update the user's password and resolve the Promise.

src/authProvider.ts
import { AuthProvider } from "@pankod/refine-core";

const authProvider: AuthProvider = {
...
updatePassword: ({ password }) => {
// update the user's password here
// if request is successful, resolve the Promise, otherwise reject it
return Promise.resolve();
},
...
};

Invoking the useUpdatePassword hook's mutation will call the updatePassword method, passing in the mutation's parameters as arguments. Additionally, the updatePassword method will take query parameters as arguments from the URL as well.

Refer to the useUpdatePassword documentation for more information

For example, if you want to update the user's password, you can use the useUpdatePassword hook like this:

import { useUpdatePassword } from "@pankod/refine-core";

const { mutate } = useUpdatePassword();

const handleUpdatePassword = ({ password, confirmPassword }) => {
mutate({ password, confirmPassword }});
};

If we assume that the URL is http://localhost:3000/reset-password?token=123, the updatePassword method will get the mutation's parameters as arguments and token query parameter as well.

const authProvider: AuthProvider = {
...
updatePassword: ({ password, confirmPassword, token }) => {
console.log(token); // 123
return Promise.resolve();
}
}

Can I pass any parameters to the updatePassword method?

Yes, you can pass any parameters to the updatePassword method. useUpdatePassword hook's mutation will pass the mutation's parameters to the updatePassword method without any type constraints.

const { mutate } = useUpdatePassword<{
password: string;
newPassword: string;
}>();
How can I redirect the user to a specific page after updating the password?

By default, the user won't be redirected to anywhere after updating the password. If you want to redirect the user to a specific page, you can resolve the updatePassword method's Promise with the path of the page.

const authProvider: AuthProvider = {
...
updatePassword: () => {
...
return Promise.resolve("/custom-page");
}
}

Also, you can use the useUpdatePassword hook's for this purpose.

const { mutate } = useUpdatePassword();

useUpdatePassword({ redirectPath: "/custom-page" });

Then, you can use the redirectPath parameter in the updatePassword method to redirect the user to the specific page.

const authProvider: AuthProvider = {
...
updatePassword: ({ redirectPath }) => {
...
return Promise.resolve(redirectPath);
}
}
How can I customize the error message?

refine automatically displays an error notification when the updatePassword method rejects the Promise. If you want to customize the error message, you can reject the Promise with an object that has name and message properties.

src/authProvider.ts
const authProvider: AuthProvider = {
...
updatePassword: () => {
...
return Promise.reject({
name: "Error",
message: "Something went wrong!",
});
}
}

Setting Authorization Credentials

After a user logs in, you can save the user's authorization credentials (such as a token) to the browser's localStorage or sessionStorage. This allows you to include the credentials in API calls by configuring the dataProvider.

Here's an example using axios and the localStorage to add a token acquired from the login method to the Authorization header of API calls.

App.tsx
...
import axios from "axios";

const axiosInstance = axios.create();

const mockUsers = [
{ username: "admin", token: "123" },
{ username: "editor", token: "321" }
];

const App = () => {
const authProvider: AuthProvider = {
login: ({ username, password }) => {
// Suppose we actually send a request to the back end here.
const user = mockUsers.find((item) => item.username === username);

if (user) {
localStorage.setItem("auth", JSON.stringify(user));
// This sets the authorization headers on Axios instance
axiosInstance.defaults.headers.common = {
Authorization: `Bearer ${user.token}`,
};

return Promise.resolve();
}
return Promise.reject();
},
...
};

return (
<Refine
authProvider={authProvider}
routerProvider={routerProvider}
// In order to use the axios instance, we need to pass it to the dataProvider
dataProvider={dataProvider(API_URL, axiosInstance)}
/>
);
}
note

We recommend using axios as the HTTP client with the @pankod/refine-simple-rest dataProvider. Other HTTP clients can also be preferred.


You can also use axios.interceptors.request.use to add the token acquired from the login method to the Authorization header of API calls. It is similar to the above example, but it is more flexible for more complex use cases such as refreshing tokens when they expire.

Refer to the axios documentation for more information about interceptors

App.tsx
...
import axios, { AxiosRequestConfig } from "axios";

const axiosInstance = axios.create();

axiosInstance.interceptors.request.use(
(request: AxiosRequestConfig) => {
// Retrieve the token from local storage
const token = JSON.parse(localStorage.getItem("auth"));
// Check if the header property exists
if (request.headers) {
// Set the Authorization header if it exists
request.headers[
"Authorization"
] = `Bearer ${token}`;
} else {
// Create the headers property if it does not exist
request.headers = {
Authorization: `Bearer ${token}`,
};
}

return request;
},
);


const mockUsers = [
{ username: "admin", token: "123" },
{ username: "editor", token: "321" }
];

const App = () => {
const authProvider: AuthProvider = {
login: ({ username, password }) => {
// Suppose we actually send a request to the back end here.
const user = mockUsers.find((item) => item.username === username);

if (user) {
localStorage.setItem("auth", JSON.stringify(user));
return Promise.resolve();
}
return Promise.reject();
},
...
};

return (
<Refine
authProvider={authProvider}
routerProvider={routerProvider}
dataProvider={dataProvider(API_URL, axiosInstance)}
/>
);
}


Checklist