Skip to main content
Authentication is a critical part of most applications. Refine provides a flexible authentication system through auth providers that work with any authentication solution.

Understanding Auth Providers

An auth provider is an object containing methods that handle authentication operations. Refine consumes these methods through authentication hooks throughout your application.
import { AuthProvider } from "@refinedev/core";

const authProvider: AuthProvider = {
  // Required methods
  login: async (params) => ({ success: boolean, redirectTo?: string, error?: Error }),
  check: async (params) => ({ authenticated: boolean, redirectTo?: string, error?: Error }),
  logout: async (params) => ({ success: boolean, redirectTo?: string, error?: Error }),
  onError: async (error) => ({ redirectTo?: string, logout?: boolean, error?: Error }),
  
  // Optional methods
  register: async (params) => ({ success: boolean, redirectTo?: string, error?: Error }),
  forgotPassword: async (params) => ({ success: boolean, redirectTo?: string, error?: Error }),
  updatePassword: async (params) => ({ success: boolean, redirectTo?: string, error?: Error }),
  getPermissions: async (params) => any,
  getIdentity: async (params) => any,
};

Quick Setup

1
Create an Auth Provider
2
Start with a simple localStorage-based authentication:
3
import { AuthProvider } from "@refinedev/core";

const authProvider: AuthProvider = {
  login: async ({ email, password }) => {
    // Make API call to authenticate
    const response = await fetch("https://api.example.com/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (response.ok) {
      const { token, user } = await response.json();
      localStorage.setItem("token", token);
      localStorage.setItem("user", JSON.stringify(user));
      
      return {
        success: true,
        redirectTo: "/",
      };
    }

    return {
      success: false,
      error: {
        name: "LoginError",
        message: "Invalid email or password",
      },
    };
  },

  check: async () => {
    const token = localStorage.getItem("token");
    
    if (token) {
      return {
        authenticated: true,
      };
    }

    return {
      authenticated: false,
      redirectTo: "/login",
      error: {
        name: "AuthError",
        message: "Not authenticated",
      },
    };
  },

  logout: async () => {
    localStorage.removeItem("token");
    localStorage.removeItem("user");
    
    return {
      success: true,
      redirectTo: "/login",
    };
  },

  onError: async (error) => {
    if (error.status === 401 || error.status === 403) {
      return {
        logout: true,
        redirectTo: "/login",
        error,
      };
    }

    return {};
  },

  getIdentity: async () => {
    const user = localStorage.getItem("user");
    
    if (user) {
      return JSON.parse(user);
    }

    return null;
  },

  getPermissions: async () => {
    const user = localStorage.getItem("user");
    
    if (user) {
      const { roles } = JSON.parse(user);
      return roles;
    }

    return null;
  },
};

export default authProvider;
4
Configure Refine
5
import { Refine, Authenticated } from "@refinedev/core";
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
import routerProvider from "@refinedev/react-router";

import authProvider from "./authProvider";
import { LoginPage } from "./pages/login";
import { Dashboard } from "./pages/dashboard";

const App = () => (
  <BrowserRouter>
    <Refine
      authProvider={authProvider}
      routerProvider={routerProvider}
      resources={[
        { name: "posts", list: "/posts" },
      ]}
    >
      <Routes>
        <Route
          element={
            <Authenticated
              key="authenticated-routes"
              fallback={<Navigate to="/login" />}
            >
              <Outlet />
            </Authenticated>
          }
        >
          <Route index element={<Dashboard />} />
          <Route path="/posts" element={<PostList />} />
        </Route>
        
        <Route
          element={
            <Authenticated key="auth-pages" fallback={<Outlet />}>
              <Navigate to="/" />
            </Authenticated>
          }
        >
          <Route path="/login" element={<LoginPage />} />
        </Route>
      </Routes>
    </Refine>
  </BrowserRouter>
);
6
Create Login Page
7
import { useLogin } from "@refinedev/core";
import { useState } from "react";

export const LoginPage = () => {
  const { mutate: login, isLoading } = useLogin();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    login({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? "Loading..." : "Login"}
      </button>
    </form>
  );
};

Authentication Methods

Login

The login method is called when users attempt to authenticate:
login: async ({ email, password, remember }) => {
  try {
    const response = await fetch("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      throw new Error("Login failed");
    }

    const { token, user } = await response.json();
    
    // Store authentication data
    if (remember) {
      localStorage.setItem("token", token);
    } else {
      sessionStorage.setItem("token", token);
    }

    return {
      success: true,
      redirectTo: "/dashboard",
    };
  } catch (error) {
    return {
      success: false,
      error: {
        name: "LoginError",
        message: error.message,
      },
    };
  }
},
Use the useLogin hook in your components:
import { useLogin } from "@refinedev/core";

const { mutate: login, isLoading, error } = useLogin();

login(
  { email: "[email protected]", password: "password" },
  {
    onSuccess: (data) => {
      console.log("Login successful", data);
    },
    onError: (error) => {
      console.error("Login failed", error);
    },
  }
);

Check Authentication

The check method verifies if a user is authenticated:
check: async () => {
  const token = localStorage.getItem("token");

  if (!token) {
    return {
      authenticated: false,
      redirectTo: "/login",
      logout: true,
    };
  }

  // Verify token is still valid
  try {
    const response = await fetch("/api/verify", {
      headers: { Authorization: `Bearer ${token}` },
    });

    if (response.ok) {
      return { authenticated: true };
    }
  } catch (error) {
    // Token is invalid
  }

  return {
    authenticated: false,
    redirectTo: "/login",
    logout: true,
    error: {
      name: "TokenExpired",
      message: "Your session has expired",
    },
  };
},
Use in components:
import { useIsAuthenticated } from "@refinedev/core";

const { data, isLoading } = useIsAuthenticated();

if (isLoading) return <div>Loading...</div>;

if (data?.authenticated) {
  return <div>Welcome back!</div>;
}

Logout

logout: async () => {
  const token = localStorage.getItem("token");

  // Notify server about logout
  if (token) {
    await fetch("/api/logout", {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
    });
  }

  // Clear local storage
  localStorage.removeItem("token");
  localStorage.removeItem("user");
  
  return {
    success: true,
    redirectTo: "/login",
  };
},
Use the hook:
import { useLogout } from "@refinedev/core";

const { mutate: logout } = useLogout();

<button onClick={() => logout()}>Logout</button>

Error Handling

The onError method handles authentication errors from API calls:
onError: async (error) => {
  // Handle 401 Unauthorized
  if (error.statusCode === 401) {
    return {
      logout: true,
      redirectTo: "/login",
      error: {
        name: "Unauthorized",
        message: "Please login again",
      },
    };
  }

  // Handle 403 Forbidden
  if (error.statusCode === 403) {
    return {
      error: {
        name: "Forbidden",
        message: "You don't have permission",
      },
    };
  }

  return {};
},

Authentication Patterns

JWT Authentication

import axios from "axios";

const axiosInstance = axios.create();

// Request interceptor - add token to all requests
axiosInstance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("token");
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor - handle token refresh
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        const refreshToken = localStorage.getItem("refreshToken");
        const response = await axios.post("/api/refresh", {
          refreshToken,
        });

        const { token } = response.data;
        localStorage.setItem("token", token);

        originalRequest.headers.Authorization = `Bearer ${token}`;
        return axiosInstance(originalRequest);
      } catch (refreshError) {
        // Refresh failed, logout user
        localStorage.removeItem("token");
        localStorage.removeItem("refreshToken");
        window.location.href = "/login";
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

OAuth / Social Login

import { AuthProvider } from "@refinedev/core";

const authProvider: AuthProvider = {
  login: async ({ providerName, code }) => {
    if (providerName === "google") {
      // Exchange code for token
      const response = await fetch("/api/auth/google", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ code }),
      });

      if (response.ok) {
        const { token, user } = await response.json();
        localStorage.setItem("token", token);
        return { success: true, redirectTo: "/" };
      }
    }

    if (providerName === "github") {
      // Similar flow for GitHub
    }

    return {
      success: false,
      error: { name: "LoginError", message: "OAuth failed" },
    };
  },
};

// In your login page
const LoginPage = () => {
  const { mutate: login } = useLogin();

  const handleGoogleLogin = () => {
    const clientId = "YOUR_GOOGLE_CLIENT_ID";
    const redirectUri = "http://localhost:3000/auth/callback";
    const scope = "email profile";
    
    window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code`;
  };

  return (
    <div>
      <button onClick={handleGoogleLogin}>Login with Google</button>
    </div>
  );
};

Role-Based Access Control

const authProvider: AuthProvider = {
  getPermissions: async () => {
    const user = localStorage.getItem("user");
    
    if (user) {
      const { roles, permissions } = JSON.parse(user);
      return { roles, permissions };
    }

    return null;
  },
};

// Use in components
import { usePermissions } from "@refinedev/core";

const { data: permissions } = usePermissions();

if (permissions?.roles?.includes("admin")) {
  return <AdminPanel />;
}

if (permissions?.permissions?.includes("posts.delete")) {
  return <DeleteButton />;
}

Advanced Features

Custom Registration

register: async ({ email, password, firstName, lastName }) => {
  try {
    const response = await fetch("/api/register", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password, firstName, lastName }),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message);
    }

    // Auto-login after registration
    const { token, user } = await response.json();
    localStorage.setItem("token", token);
    localStorage.setItem("user", JSON.stringify(user));

    return {
      success: true,
      redirectTo: "/",
    };
  } catch (error) {
    return {
      success: false,
      error: {
        name: "RegistrationError",
        message: error.message,
      },
    };
  }
},

Password Reset

forgotPassword: async ({ email }) => {
  try {
    await fetch("/api/forgot-password", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });

    return {
      success: true,
      redirectTo: "/login",
    };
  } catch (error) {
    return {
      success: false,
      error: {
        name: "ForgotPasswordError",
        message: "Failed to send reset email",
      },
    };
  }
},

updatePassword: async ({ password, confirmPassword, token }) => {
  try {
    await fetch("/api/reset-password", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ password, token }),
    });

    return {
      success: true,
      redirectTo: "/login",
    };
  } catch (error) {
    return {
      success: false,
      error: {
        name: "ResetPasswordError",
        message: "Failed to reset password",
      },
    };
  }
},

Get User Identity

getIdentity: async () => {
  const token = localStorage.getItem("token");
  
  if (!token) return null;

  try {
    const response = await fetch("/api/me", {
      headers: { Authorization: `Bearer ${token}` },
    });

    if (response.ok) {
      return await response.json();
    }
  } catch (error) {
    console.error("Failed to fetch user identity", error);
  }

  return null;
},
Use in your app:
import { useGetIdentity } from "@refinedev/core";

const Header = () => {
  const { data: user } = useGetIdentity();

  return (
    <header>
      <span>Welcome, {user?.name}</span>
      <img src={user?.avatar} alt={user?.name} />
    </header>
  );
};

Integration Examples

Auth0

import { Auth0Client } from "@auth0/auth0-spa-js";

const auth0Client = new Auth0Client({
  domain: "YOUR_DOMAIN",
  client_id: "YOUR_CLIENT_ID",
  redirect_uri: window.location.origin,
});

const authProvider: AuthProvider = {
  login: async () => {
    await auth0Client.loginWithRedirect();
    return { success: true };
  },
  
  check: async () => {
    const isAuthenticated = await auth0Client.isAuthenticated();
    
    if (isAuthenticated) {
      return { authenticated: true };
    }

    return { authenticated: false, redirectTo: "/login" };
  },
  
  logout: async () => {
    await auth0Client.logout({
      returnTo: window.location.origin,
    });
    return { success: true };
  },
  
  getIdentity: async () => {
    const user = await auth0Client.getUser();
    return user;
  },
};

Supabase

import { createClient } from "@supabase/supabase-js";

const supabase = createClient("YOUR_URL", "YOUR_ANON_KEY");

const authProvider: AuthProvider = {
  login: async ({ email, password }) => {
    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      return {
        success: false,
        error: { name: "LoginError", message: error.message },
      };
    }

    return { success: true, redirectTo: "/" };
  },
  
  check: async () => {
    const { data: { session } } = await supabase.auth.getSession();
    
    if (session) {
      return { authenticated: true };
    }

    return { authenticated: false, redirectTo: "/login" };
  },
  
  logout: async () => {
    const { error } = await supabase.auth.signOut();
    
    if (error) {
      return {
        success: false,
        error: { name: "LogoutError", message: error.message },
      };
    }

    return { success: true, redirectTo: "/login" };
  },
};

Best Practices

  1. Never store sensitive data in localStorage: Use httpOnly cookies for tokens when possible
  2. Implement token refresh: Prevent users from being logged out unexpectedly
  3. Handle errors gracefully: Provide clear error messages to users
  4. Use HTTPS: Always use secure connections in production
  5. Implement rate limiting: Protect against brute force attacks
  6. Validate on both client and server: Never trust client-side validation alone

Next Steps

Build docs developers (and LLMs) love