Skip to main content

Authentication & Role-Based Access

Right now, StratoShop is wide open — anyone can access it. In this chapter, we will add an Auth Provider to require login and then use permissions to restrict what different users can do.

The Auth Provider

An Auth Provider is a plain object that implements the authentication contract between your app and your backend. Pass it to <Admin> via the authProvider prop.

Here is a minimal implementation for a token-based API. Create src/authProvider.ts:

src/authProvider.ts
import type { AuthProvider } from '@strato-admin/admin';

export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});

if (!response.ok) {
throw new Error('Invalid credentials');
}

const { token, role } = await response.json();
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_role', role);
},

logout: async () => {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_role');
},

checkAuth: async () => {
if (!localStorage.getItem('auth_token')) {
throw new Error('Not authenticated');
}
},

checkError: async (error) => {
if (error.status === 401 || error.status === 403) {
localStorage.removeItem('auth_token');
throw error;
}
},

getPermissions: async () => {
return localStorage.getItem('auth_role') ?? 'viewer';
},
};

Wire it into your app:

src/App.tsx
import { authProvider } from './authProvider';

const App = () => (
<Admin dataProvider={dataProvider} authProvider={authProvider}>
{/* ... */}
</Admin>
);

With this in place, unauthenticated users are automatically redirected to a login page. Strato Admin provides a default login form — you don't need to build one.

How Authentication Works

When authProvider is present, Strato Admin calls checkAuth before rendering any protected page. If it throws, the user is sent to /login. After a successful login call, they are redirected back to the page they tried to access.

The checkError method is called whenever a data provider request fails. If you throw from it (for 401/403 responses), the user is logged out automatically.

Role-Based Access Control

The getPermissions method returns whatever your backend uses to represent a user's role — a string, an array, or an object. You can then use this in your resource definitions to conditionally disable destructive actions.

Hiding the Delete Button

Use the delete prop on <ResourceSchema> to disable delete access. For dynamic control based on the logged-in user's role, read permissions using the usePermissions hook in a wrapper component:

src/resources/products.tsx
import { usePermissions, ResourceSchema, TextField, NumberField } from '@strato-admin/admin';

export const ProductResource = () => {
const { permissions } = usePermissions();
const canDelete = permissions === 'admin';

return (
<ResourceSchema name="products" delete={canDelete} listInclude={['name', 'price', 'category_id', 'status']}>
<TextField source="id" />
<TextField source="name" />
<NumberField source="price" />
<TextField source="status" />
</ResourceSchema>
);
};

Update App.tsx to render it as a component rather than a static JSX element:

src/App.tsx
const App = () => (
<Admin dataProvider={dataProvider} authProvider={authProvider}>
<ProductResource />
{/* other resources */}
</Admin>
);

Note: Setting delete={false} hides the delete button and disables the bulk delete action in the UI. It does not prevent direct API calls — always enforce permissions on the server as well.

Quick Start: Mock Auth Provider

For development or demos, you can use a simple mock that accepts any credentials:

src/authProvider.ts
export const authProvider: AuthProvider = {
login: async ({ username }) => {
localStorage.setItem('auth_role', username === 'admin' ? 'admin' : 'editor');
},
logout: async () => localStorage.clear(),
checkAuth: async () => {
if (!localStorage.getItem('auth_role')) throw new Error();
},
checkError: async (error) => {
if (error.status === 401) throw error;
},
getPermissions: async () => localStorage.getItem('auth_role') ?? 'viewer',
};

Log in with username admin to get the admin role, or any other username for the editor role.

Summary

In this chapter, we've learned how to:

  • Implement an AuthProvider to add login/logout to the dashboard.
  • Use checkAuth and checkError to protect routes and handle expired sessions.
  • Use getPermissions and usePermissions to apply role-based access control.

Next Steps

In the next chapter, we will build a custom dashboard using Strato's headless hooks and Cloudscape chart components.