Skip to content

Security & Auth Providers

Shadcn-Admin-Kit supports both authentication and authorization, allowing you to secure your admin app with your preferred authentication strategy. Since there are many strategies (e.g., OAuth, MFA, passwordless, magic link), shadcn-admin-kit delegates this logic to an authProvider.

This documentation will explain the following concepts:

The authProvider acts as a bridge between shadcn-admin-kit and the authentication backend.

An Auth Provider must implement the following methods:

const authProvider = {
// Send username and password to the auth server and get back credentials
async login(params) {/** ... **/},
// Check if an error from the dataProvider indicates an authentication issue
async checkError(error) {/** ... **/},
// Verify that the user's credentials are still valid during navigation
async checkAuth(params) {/** ... **/},
// Remove local credentials and notify the auth server of the logout
async logout() {/** ... **/},
// Retrieve the user's profile
async getIdentity() {/** ... **/},
// (Optional) Check if the user has permission for a specific action on a resource
async canAccess() {/** ... **/},
};

You can use an existing Auth Provider from the List of Available Auth Providers or create your own.

Once you set an <Admin authProvider>, shadcn-admin-kit enables authentication automatically. For example, to use Auth0, you can set up the authProvider like this:

import { BrowserRouter } from 'react-router';
import { Auth0AuthProvider } from 'ra-auth-auth0';
import { Auth0Client } from '@auth0/auth0-spa-js';
import { Admin } from '@/components/admin';
const auth0 = new Auth0Client({
domain: import.meta.env.VITE_AUTH0_DOMAIN,
clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
cacheLocation: 'localstorage',
authorizationParams: {
audience: import.meta.env.VITE_AUTH0_AUDIENCE,
},
});
const authProvider = Auth0AuthProvider(auth0, {
loginRedirectUri: import.meta.env.VITE_LOGIN_REDIRECT_URL,
logoutRedirectUri: import.meta.env.VITE_LOGOUT_REDIRECT_URL,
});
const App = () => (
<BrowserRouter>
<Admin authProvider={authProvider}>
...
</Admin>
</BrowserRouter>
);

Now, every page that requires authentication will redirect the user to the login page if they are not authenticated. After successful login, the user will be redirected back to the page they were trying to access.

Check out the Auth Provider Setup documentation for more details about sending credentials to the API, allowing anonymous access to certain pages, handling refresh tokens, and more.

The community has built a few open-source Auth Providers that may fit your need:

If you need to use an auth backend that isn’t listed here, you can create your own authProvider by implementing the methods described above. Check out the Writing an Auth Provider guide for more details.

Once a user is authenticated, your application may need to check if the user has the right to access a specific resource or perform a particular action.

With Access Control, the authProvider is responsible for checking if the user can access a specific resource or perform a particular action. This flexibility allows you to implement various authorization strategies, such as:

  • Role-Based Access Control (RBAC)
  • Attribute-Based Access Control (ABAC)
  • Access Control List (ACL).

Use the authProvider to integrate shadcn-admin-kit with popular authorization solutions like Okta, Casbin, Cerbos, and more.

To use Access Control, the authProvider must implement a canAccess method with the following signature:

type CanAccessParams = {
action: string;
resource: string;
record?: any;
};
async function canAccess(params: CanAccessParams): Promise<boolean>;

React components will use this method to determine if the current user can perform an action (e.g., “read”, “update”, “delete”) on a particular resource (e.g., “posts”, “posts.title”, etc.) and optionally on a specific record (to implement record-level permissions).

For example, let’s assume that the application receives a list of authorized resources on login. The authProvider would look like this:

const authProvider = {
async login({ username, password }) {
// ...
const permissions = await fetchPermissions();
// permissions look like
// ['posts', 'comments', 'users']
localStorage.setItem('permissions', JSON.stringify(permissions));
},
async logout() {
// ...
localStorage.removeItem('permissions');
},
async canAccess({ resource }) {
const permissions = JSON.parse(localStorage.getItem('permissions'));
return permissions.some(p => p.resource === resource);
},
};

canAccess can be asynchronous, so if the authProvider needs to fetch the permissions from a server or refresh a token, it can return a promise.

Tip: Shadcn-admin-kit calls dataProvider.canAccess() before rendering all page components, so if the call is slow, user navigation may be delayed. If you can, fetch user permissions on login and store them locally to keep access control fast.

The page components (<List>, <Create>, <Edit>, and <Show>) have built-in access control. Before rendering them, shadcn-admin-kit calls authProvider.canAccess() with the appropriate action and resource parameters.

<Resource
name="posts"
// available if canAccess({ action: 'list', resource: 'posts' }) returns true
list={PostList}
// available if canAccess({ action: 'create', resource: 'posts' }) returns true
create={PostCreate}
// available if canAccess({ action: 'edit', resource: 'posts' }) returns true
edit={PostEdit}
// available if canAccess({ action: 'show', resource: 'posts' }) returns true
show={PostShow}
/>

If the authProvider doesn’t implement the canAccess method, shadcn-admin-kit assumes the user can access all pages.

To learn more about implementing access control, check out the Access Control Guide.

Role-Based Access Control requires a valid Enterprise Edition subscription.

Terminal window
npm install --save @react-admin/ra-core-ee
# or
yarn add @react-admin/ra-core-ee

getPermissionsFromRoles returns an array of user permissions based on a role definition, a list of roles, and a list of user permissions. It merges the permissions defined in roleDefinitions for the current user’s roles (userRoles) with the extra userPermissions.

It is a builder block to implement the authProvider.canAccess() method, which is called by ra-core to check whether the current user has the right to perform a given action on a given resource or record.

getPermissionsFromRoles takes a configuration object as argument containing the role definitions, the user roles, and the user permissions.

It returns an array of permissions that can be passed to canAccessWithPermissions.

import { getPermissionsFromRoles } from '@react-admin/ra-core-ee';
// static role definitions (usually in the app code)
const roleDefinitions = {
admin: [{ action: '*', resource: '*' }],
reader: [
{ action: ['list', 'show', 'export'], resource: '*' },
{ action: 'read', resource: 'posts.*' },
{ action: 'read', resource: 'comments.*' },
],
accounting: [{ action: '*', resource: 'sales' }],
};
const permissions = getPermissionsFromRoles({
roleDefinitions,
// roles of the current user (usually returned by the server upon login)
userRoles: ['reader'],
// extra permissions for the current user (usually returned by the server upon login)
userPermissions: [{ action: 'list', resource: 'sales' }],
});
// permissions = [
// { action: ['list', 'show', 'export'], resource: '*' },
// { action: 'read', resource: 'posts.*' },
// { action: 'read', resource: 'comments.*' },
// { action: 'list', resource: 'sales' },
// ];

This function takes an object as argument with the following fields:

NameOptionalTypeDescription
roleDefinitionsRequiredRecord<string, Permission>A dictionary containing the role definition for each role
userRolesOptionalArray<string>An array of roles (admin, reader…) for the current user
userPermissionsOptionalArray<Permission>An array of permissions for the current user

canAccessWithPermissions is a helper function that facilitates the implementation of Access Control policies based on an underlying list of user roles and permissions.

It is a builder block to implement the authProvider.canAccess() method, which is called by ra-core to check whether the current user has the right to perform a given action on a given resource or record.

canAccessWithPermissions is a pure function that you can call from your authProvider.canAccess() implementation.

import { canAccessWithPermissions } from '@react-admin/ra-core-ee';
const authProvider = {
// ...
canAccess: async ({ action, resource, record }) => {
const permissions = myGetPermissionsFunction();
return canAccessWithPermissions({
permissions,
action,
resource,
record,
});
},
// ...
};

The permissions parameter must be an array of permissions. A permission is an object that represents access to a subset of the application. It is defined by a resource (usually a noun) and an action (usually a verb), with sometimes an additional record.

Here are a few examples of permissions:

  • { action: "*", resource: "*" }: allow everything
  • { action: "read", resource: "*" }: allow read actions on all resources
  • { action: "read", resource: ["companies", "people"] }: allow read actions on a subset of resources
  • { action: ["read", "create", "edit", "export"], resource: "companies" }: allow all actions except delete on companies
  • { action: ["write"], resource: "game.score", record: { "id": "123" } }: allow write action on the score of the game with id 123

In most cases, the permissions are derived from user roles, which are fetched at login and stored in memory or in localStorage. Check the getPermissionsFromRoles function to merge the permissions from multiple roles into a single flat array of permissions.

This function takes an object as argument with the following fields:

NameOptionalTypeDescription
permissionsRequiredArray<Permission>An array of permissions for the current user
actionRequiredstringThe action for which to check users has the execution right
resourceRequiredstringThe resource for which to check users has the execution right
recordRequiredstringThe record for which to check users has the execution right

canAccessWithPermissions expects the permissions to be a flat array of permissions. It is your responsibility to fetch these permissions (usually during login). If the permissions are spread into several role definitions, you can merge them into a single array using the getPermissionsFromRoles function.

The following example shows how to implement Role-based Access Control (RBAC) in authProvider.canAccess() using canAccessWithPermissions and getPermissionsFromRoles. The role permissions are defined in the code, and the user roles are returned by the authentication endpoint. Additional user permissions can also be returned by the authentication endpoint.

The authProvider stores the permissions in localStorage, so that returning users can access their permissions without having to log in again.

// in roleDefinitions.ts
export const roleDefinitions = {
admin: [{ action: '*', resource: '*' }],
reader: [
{ action: ['list', 'show', 'export'], resource: '*' },
{ action: 'read', resource: 'posts.*' },
{ action: 'read', resource: 'comments.*' },
],
accounting: [{ action: '*', resource: 'sales' }],
};
// in authProvider.ts
import {
canAccessWithPermissions,
getPermissionsFromRoles,
} from '@react-admin/ra-core-ee';
import { roleDefinitions } from './roleDefinitions';
const authProvider = {
login: async ({ username, password }) => {
const request = new Request('https://mydomain.com/authenticate', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
const response = await fetch(request);
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
const {
user: { roles, permissions },
} = await response.json();
// merge the permissions from the roles with the extra permissions
const permissions = getPermissionsFromRoles({
roleDefinitions,
userPermissions,
userRoles,
});
localStorage.setItem('permissions', JSON.stringify(permissions));
},
canAccess: async ({ action, resource, record }) => {
const permissions = JSON.parse(localStorage.getItem('permissions'));
return canAccessWithPermissions({
permissions,
action,
resource,
record,
});
},
// ...
};