Add authentication to your React app with WorkOS AuthKit. Handles sign-in, sign-up, token refresh, and session management via a hosted OAuth flow.
npm install @workos-inc/authkit-react- Add a Redirect URI (e.g.
http://localhost:5173) on the Redirects page. This is where WorkOS sends users after they authenticate. - Add your app's origin (e.g.
http://localhost:5173) to the allowed origins list on the Authentication page of the WorkOS Dashboard.
import{AuthKitProvider}from"@workos-inc/authkit-react";import{createRoot}from"react-dom/client";createRoot(document.getElementById("root")).render(<AuthKitProviderclientId="client_01ABC123DEF456"><App/></AuthKitProvider>,);Some authentication flows are initiated outside your app — for example, when an admin impersonates a user from the WorkOS Dashboard, or when a third-party triggers login via your app's Sign-in URL. These flows redirect the user to a well-known login path in your app, which must then start the OAuth flow.
Register a /login URL (e.g. http://localhost:5173/login) as the Sign-in URL on the same Redirects page, then handle it in your app:
import{useAuth}from"@workos-inc/authkit-react";import{useEffect}from"react";functionLoginRoute(){const{ signIn }=useAuth();useEffect(()=>{signIn();},[signIn]);return<div>Redirecting...</div>;}In a router-based app this would be a dedicated route component. In a simple app you can handle it inline:
functionApp(){const{ isLoading, user, signIn }=useAuth();useEffect(()=>{if(window.location.pathname==="/login"){signIn();}},[signIn]);// ... rest of your app}import{useAuth}from"@workos-inc/authkit-react";functionApp(){const{ isLoading, user, signIn, signUp, signOut, getAccessToken }=useAuth();if(isLoading)return<div>Loading...</div>;if(!user){return(<div><buttononClick={()=>signIn()}>Sign In</button><buttononClick={()=>signUp()}>Sign Up</button></div>);}return(<div><p>Hello, {user.firstName??user.email}</p><buttononClick={()=>signOut()}>Sign Out</button></div>);}That's it — you have a fully authenticated React app.
Wrap your app in this component to provide authentication context.
| Prop | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | Your WorkOS Client ID (starts with client_) |
apiHostname | string | No | Your custom Authentication API domain. Defaults to api.workos.com. In production, this should be set to a domain you own (e.g. auth.example.com) |
devMode | boolean | No | Stores tokens in localStorage. Auto-enabled on localhost and 127.0.0.1. |
onRedirectCallback | (params) => void | No | Called after a successful authentication. Use to restore app state or navigate. |
onRefresh | (response) => void | No | Called when the access token is refreshed. |
onRefreshFailure | ({ signIn }) => void | No | Called when token refresh fails. Receives signIn to trigger re-authentication. |
onBeforeAutoRefresh | () => boolean | No | Called before automatic refresh. Return false to skip. |
refreshBufferInterval | number | No | Seconds before token expiration to trigger refresh. |
Returns the current auth state and helper methods. Must be called inside <AuthKitProvider>.
| Property | Type | Description |
|---|---|---|
isLoading | boolean | true during initial authentication check |
user | User | null | The authenticated user, or null |
organizationId | string | null | The user's current organization |
role | string | null | The user's role in the current organization |
roles | string[] | null | All roles for the user in the current organization |
permissions | string[] | Permissions for the user's role |
featureFlags | string[] | Feature flags enabled for the organization |
impersonator | Impersonator | null | Set when an admin is impersonating this user |
authenticationMethod | AuthenticationMethod | null | How the user authenticated (e.g. "GoogleOAuth", "SSO") |
| Method | Signature | Description |
|---|---|---|
signIn | (opts?) => Promise<void> | Redirect to the AuthKit sign-in page |
signUp | (opts?) => Promise<void> | Redirect to the AuthKit sign-up page |
signOut | (opts?) => void | End the session and sign the user out |
getAccessToken | (opts?) => Promise<string> | Get a valid access token, refreshing if needed |
getUser | () => User | null | Synchronously get the current user |
switchToOrganization | ({ organizationId, signInOpts? }) => Promise<void> | Switch to a different organization |
getSignInUrl | (opts?) => Promise<string> | Get the sign-in URL without redirecting |
getSignUpUrl | (opts?) => Promise<string> | Get the sign-up URL without redirecting |
{state?: any;// Data to persist through the auth flow
organizationId?: string;// Pre-select an organization
loginHint?: string;// Pre-fill the email field
invitationToken?: string;// Accept an invitation during sign-up
screenHint?: "sign-in"|"sign-up";// Which screen to show}{returnTo?: string;// URL to navigate to after sign-out}interfaceUser{id: string;email: string;emailVerified: boolean;profilePictureUrl: string|null;firstName: string|null;lastName: string|null;createdAt: string;updatedAt: string;lastSignInAt: string|null;externalId: string|undefined;}Decodes a JWT access token and returns its claims.
import{getClaims}from"@workos-inc/authkit-react";consttoken=awaitgetAccessToken();constclaims=getClaims(token);// claims.sub, claims.org_id, claims.role, claims.permissions, etc.AuthKitError— Base error class for AuthKit errors.LoginRequiredError— Thrown bygetAccessToken()when no user is authenticated.
functionDashboard(){const{ getAccessToken }=useAuth();asyncfunctionfetchData(){consttoken=awaitgetAccessToken();constres=awaitfetch("/api/data",{headers: {Authorization: `Bearer ${token}`},});returnres.json();}// ...}Use state to preserve data across the authentication redirect:
// Pass state when starting sign-in<buttononClick={()=>signIn({state: {returnTo: "/dashboard"}})}>
Sign In
</button>Security:
stateround-trips through the OAuth redirect as plaintext in the URL and is not integrity protected — treat anything read from it as untrusted input. Before navigating to areturnTo-style value, validate it against your own origin so an attacker cannot smuggle ajavascript:URI or an off-site open-redirect target.
// Retrieve it in onRedirectCallback<AuthKitProviderclientId="client_01ABC123DEF456"onRedirectCallback={({ state })=>{if(typeofstate?.returnTo!=="string")return;leturl;try{url=newURL(state.returnTo,window.location.origin);}catch{return;// malformed URL — ignore}// Only navigate to a same-origin destination. Use the parsed absolute// URL, not a value rebuilt from url.pathname (a "//evil.com" pathname// would redirect off-site).if(url.origin===window.location.origin){window.location.href=url.href;}}}><App/></AuthKitProvider><AuthKitProviderclientId="client_01ABC123DEF456"onRefreshFailure={({ signIn })=>{// Session expired — prompt re-authenticationsignIn();}}><App/></AuthKitProvider>functionOrgSwitcher({ organizations }){const{ switchToOrganization, organizationId }=useAuth();return(<selectvalue={organizationId??""}onChange={(e)=>switchToOrganization({organizationId: e.target.value})}>{organizations.map((org)=>(<optionkey={org.id}value={org.id}>{org.name}</option>))}</select>);}functionAdminPanel(){const{ role, permissions }=useAuth();if(role!=="admin")returnnull;return(<div><h2>Admin Panel</h2>{permissions.includes("users:manage")&&<UserManagement/>}</div>);}