August 14, 2026
Secure a React SPA with WSO2 Identity Platform and `@asgardeo/react`
Register a single-page application, connect the current React SDK, render authenticated UI safely, and understand the OAuth boundary you…

By Sanjula Herath
5 min read
Register a single-page application, connect the current React SDK, render authenticated UI safely, and understand the OAuth boundary you are creating.
Authentication code has a habit of looking deceptively small.
A login button redirects somewhere, a callback returns with a code, and the application shows a user profile. Then the production questions arrive: Where should tokens live? How is logout completed? Can an attacker reuse a redirect URI? Which component owns session state? How does the frontend call a protected API without leaking credentials?
That is why identity is better treated as infrastructure than as a collection of UI callbacks.
WSO2 has renamed Asgardeo to WSO2 Identity Platform. During the transition, current endpoints and packages still contain the Asgardeo name. The current React integration uses the @asgardeo/react package and an <AsgardeoProvider />; older articles built around @asgardeo/auth-react should not be copied blindly.
This walkthrough builds a Vite-based React application that can sign in, sign out, and display the authenticated user's profile.
What problem does the platform solve?
A browser application is a public OAuth client: it cannot keep a client secret. A secure implementation still needs to coordinate several responsibilities:
- register exact redirect and post-logout destinations;
- start an OpenID Connect authorization flow;
- correlate the callback with the request that initiated it;
- maintain authenticated state across React renders;
- retrieve identity claims;
- attach access tokens only to the intended API calls; and
- clear both local and identity-provider sessions during logout.
The SDK places those mechanics behind a React context and declarative components. WSO2 Identity Platform supplies the authorization server, login experience, user directory, application registration, consent, session management, and policy controls.
The important architectural boundary remains: the SDK improves authentication ergonomics, but the API — not the React UI — must enforce authorization.
Architecture in one request flow
- The user clicks Sign in in the SPA.
- The browser is redirected to WSO2 Identity Platform.
- WSO2 authenticates the user according to the application's login flow.
- The browser returns only to a registered redirect URL.
- The SDK completes the OIDC flow and updates React authentication state.
- Signed-in components render profile data.
- When the SPA calls an API, it sends an access token; the API validates that token and its permissions.
This separation matters. An ID token describes authentication to the client. An access token authorizes a call to a resource server. Treating either token as a generic session string is how subtle security bugs begin.
Standout capabilities
1. Declarative authentication state
<SignedIn> and <SignedOut> make the UI state explicit. They prevent every component from reimplementing loading, authenticated, and unauthenticated branches.
2. A single provider boundary
<AsgardeoProvider> supplies the client ID and organization base URL once. Components below it consume a shared identity context instead of creating multiple SDK instances.
3. Prebuilt identity components
The SDK includes sign-in, sign-out, user, user-dropdown, and user-profile components. Teams can start with the safe default and replace presentation incrementally.
4. Standards-based integration
The application registration is based on OAuth 2.0 and OpenID Connect rather than a WSO2-only login protocol. That keeps the frontend/resource-server boundary understandable to developers already familiar with OIDC.
Hands-on walkthrough
Step 1: Register the React application
In the WSO2 Identity Platform Console:
- Open Applications → New Application.
- Select React.
- Name the application
inventory-console. - Set the authorized redirect URL to
[http://localhost:5173](http://localhost:5173.). - Finish the wizard and copy the Client ID and Base URL.
The base URL normally follows this shape:
https://api.asgardeo.io/t/<your-organization-name>https://api.asgardeo.io/t/<your-organization-name>The redirect URI is a security control, not documentation. Use the exact local URL above for development and register the exact HTTPS production URL before deployment. Avoid permissive wildcard callbacks.
Step 2: Create the React app
npm create vite@latest inventory-console -- --template react
cd inventory-console
npm install
npm install @asgardeo/react
npm run devnpm create vite@latest inventory-console -- --template react
cd inventory-console
npm install
npm install @asgardeo/react
npm run devVite serves the application at http://localhost:5173 by default, which must match the registered callback.
Step 3: Add the provider
Create a local environment file:
# .env.local — do not place confidential values here.
# A SPA client ID is public; a client secret must never be shipped to the browser.
VITE_WSO2_CLIENT_ID=replace-with-your-app-client-id
VITE_WSO2_BASE_URL=https://api.asgardeo.io/t/your-organization-name# .env.local — do not place confidential values here.
# A SPA client ID is public; a client secret must never be shipped to the browser.
VITE_WSO2_CLIENT_ID=replace-with-your-app-client-id
VITE_WSO2_BASE_URL=https://api.asgardeo.io/t/your-organization-nameUpdate src/main.jsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AsgardeoProvider } from "@asgardeo/react";
import App from "./App.jsx";
import "./index.css";
const clientId = import.meta.env.VITE_WSO2_CLIENT_ID;
const baseUrl = import.meta.env.VITE_WSO2_BASE_URL;
if (!clientId || !baseUrl) {
throw new Error("Missing WSO2 Identity Platform configuration");
}
createRoot(document.getElementById("root")).render(
<StrictMode>
<AsgardeoProvider clientId={clientId} baseUrl={baseUrl}>
<App />
</AsgardeoProvider>
</StrictMode>
);import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AsgardeoProvider } from "@asgardeo/react";
import App from "./App.jsx";
import "./index.css";
const clientId = import.meta.env.VITE_WSO2_CLIENT_ID;
const baseUrl = import.meta.env.VITE_WSO2_BASE_URL;
if (!clientId || !baseUrl) {
throw new Error("Missing WSO2 Identity Platform configuration");
}
createRoot(document.getElementById("root")).render(
<StrictMode>
<AsgardeoProvider clientId={clientId} baseUrl={baseUrl}>
<App />
</AsgardeoProvider>
</StrictMode>
);The provider is the identity boundary for the component tree. Moving it inside a frequently re-rendered component risks recreating state; keep it at the root.
Step 4: Render signed-in and signed-out experiences
Replace src/App.jsx:
import {
SignedIn,
SignedOut,
SignInButton,
SignOutButton,
User,
UserDropdown
} from "@asgardeo/react";
import "./App.css";
export default function App() {
return (
<div className="shell">
<header className="topbar">
<h1>Inventory Console</h1>
<SignedOut>
<SignInButton>Sign in</SignInButton>
</SignedOut>
<SignedIn>
<UserDropdown />
<SignOutButton>Sign out</SignOutButton>
</SignedIn>
</header>
<main>
<SignedOut>
<p>Sign in to view warehouse inventory.</p>
</SignedOut>
<SignedIn>
<User>
{(user) => (
<section>
<h2>
Welcome, {user.userName || user.username || user.sub}
</h2>
<p>Your authenticated inventory workspace is ready.</p>
</section>
)}
</User>
</SignedIn>
</main>
</div>
);
}import {
SignedIn,
SignedOut,
SignInButton,
SignOutButton,
User,
UserDropdown
} from "@asgardeo/react";
import "./App.css";
export default function App() {
return (
<div className="shell">
<header className="topbar">
<h1>Inventory Console</h1>
<SignedOut>
<SignInButton>Sign in</SignInButton>
</SignedOut>
<SignedIn>
<UserDropdown />
<SignOutButton>Sign out</SignOutButton>
</SignedIn>
</header>
<main>
<SignedOut>
<p>Sign in to view warehouse inventory.</p>
</SignedOut>
<SignedIn>
<User>
{(user) => (
<section>
<h2>
Welcome, {user.userName || user.username || user.sub}
</h2>
<p>Your authenticated inventory workspace is ready.</p>
</section>
)}
</User>
</SignedIn>
</main>
</div>
);
}Run the app, choose Sign in, complete authentication, and confirm that the profile-aware branch replaces the public branch.
Protecting UI is not protecting data
<SignedIn> is useful presentation logic. It is not an authorization control. A user can modify JavaScript in their own browser and call an API directly.
For every protected API request, the backend should validate at least:
- the token signature against trusted keys;
- issuer (
iss); - audience (
aud); - expiration and not-before times;
- the scopes, roles, or permissions required by the operation; and
- any tenant or organization context used by the application.
The frontend may hide an Approve Purchase button when a user lacks a permission, but the purchase API must make the final decision.
Production hardening checklist
- Register only HTTPS redirect and logout URLs in production.
- Never put a client secret in Vite variables or bundled JavaScript.
- Keep access tokens out of URLs, logs, analytics events, and error reports.
- Apply a Content Security Policy and treat XSS as a token-compromise risk.
- Configure CORS at the API for the actual frontend origins.
- Validate authorization at the API, even when the UI already checks a role.
- Test sign-out across tabs and verify that protected data is removed from application state.
- Use short token lifetimes appropriate for the risk of the application.
How it compares
Option Best fit Trade-off WSO2 Identity Platform Teams wanting managed CIAM/IAM, standards-based login, adaptive flows, and the WSO2 ecosystem Cloud service and WSO2-specific administration model WSO2 Identity Server Teams requiring self-managed identity infrastructure and deeper runtime control More deployment, upgrades, and operational ownership Auth0 or Okta Customer Identity Teams already standardized on those SaaS ecosystems Different extension, pricing, and governance models Keycloak Teams prioritizing an open-source, self-hosted identity provider The team owns availability, upgrades, scaling, and hardening Custom OAuth code Appropriate only for specialized protocol work Highest security and maintenance burden
The useful comparison is not "which login button is shortest?" It is which platform matches your data-residency, customization, standards, operating model, and lifecycle requirements.
Key takeaways
- Current WSO2 React applications should use
@asgardeo/reactand<AsgardeoProvider>. - The provider centralizes SDK state; declarative components simplify authenticated UI.
- A SPA client ID is public, but client secrets never belong in browser code.
- UI gating improves experience; resource-server authorization provides security.
- Exact redirect URIs, backend token validation, CSP, and disciplined logging matter as much as the happy-path demo.