106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
import { useAuth } from 'react-oidc-context';
|
|
import { useState } from 'react';
|
|
import { fetchProtected } from './api/client';
|
|
import './App.css';
|
|
|
|
function App() {
|
|
const auth = useAuth();
|
|
const [apiData, setApiData] = useState<unknown>(null);
|
|
const [apiError, setApiError] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
if (auth.isLoading) {
|
|
return <div className="app">Loading session…</div>;
|
|
}
|
|
|
|
if (auth.error) {
|
|
const hint =
|
|
auth.error.message === 'Failed to fetch'
|
|
? 'The browser could not reach Authentik. Check that it is running, VITE_AUTHENTIK_URL is correct, and the discovery URL opens in a new tab.'
|
|
: auth.error.message;
|
|
return (
|
|
<div className="app">
|
|
<p className="error">Auth error: {hint}</p>
|
|
<button type="button" onClick={() => auth.signinRedirect()}>
|
|
Try again
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!auth.isAuthenticated || !auth.user) {
|
|
return (
|
|
<div className="app">
|
|
<h1>OIDC Auth Demo</h1>
|
|
<p>Sign in with Authentik to continue.</p>
|
|
<button type="button" onClick={() => auth.signinRedirect()}>
|
|
Sign in
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const idClaims = auth.user.profile;
|
|
const accessToken = auth.user.access_token;
|
|
|
|
async function callApi() {
|
|
if (!accessToken) {
|
|
setApiError('No access token in session');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setApiError(null);
|
|
try {
|
|
const data = await fetchProtected('/api/me', accessToken);
|
|
setApiData(data);
|
|
} catch (err) {
|
|
setApiData(null);
|
|
setApiError(err instanceof Error ? err.message : 'API request failed');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="app">
|
|
<header>
|
|
<h1>OIDC Auth Demo</h1>
|
|
<button type="button" className="secondary" onClick={() => auth.signoutRedirect()}>
|
|
Sign out
|
|
</button>
|
|
</header>
|
|
|
|
<section className="card">
|
|
<h2>Login (ID Token)</h2>
|
|
<p className="hint">
|
|
User identity comes from the ID token claims below.
|
|
</p>
|
|
<dl>
|
|
<dt>Subject</dt>
|
|
<dd>{idClaims.sub}</dd>
|
|
<dt>Email</dt>
|
|
<dd>{String(idClaims.email ?? '—')}</dd>
|
|
<dt>Name</dt>
|
|
<dd>{String(idClaims.name ?? idClaims.preferred_username ?? '—')}</dd>
|
|
</dl>
|
|
</section>
|
|
|
|
<section className="card">
|
|
<h2>API (Access Token)</h2>
|
|
<p className="hint">
|
|
Protected routes use the access token in the Authorization header.
|
|
</p>
|
|
<button type="button" onClick={callApi} disabled={loading}>
|
|
{loading ? 'Calling API…' : 'GET /api/me'}
|
|
</button>
|
|
{apiError && <p className="error">{apiError}</p>}
|
|
{apiData != null && (
|
|
<pre>{JSON.stringify(apiData, null, 2)}</pre>
|
|
)}
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|