import { useEffect, useState } from 'react';
const CLIENT_ID = 'your_client_id';
const REDIRECT_URI = 'http://localhost:3000/callback';
const AUTHORITY_URL = 'https://auth.example.com';
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
// Check for callback
if (window.location.search.includes('code=')) {
handleCallback();
}
}, []);
async function login() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
const state = generateCodeVerifier();
// Store for callback
sessionStorage.setItem('code_verifier', codeVerifier);
sessionStorage.setItem('state', state);
const params = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'openid profile email',
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
window.location.href = `${AUTHORITY_URL}/authorize?${params}`;
}
async function handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
// Verify state
if (state !== sessionStorage.getItem('state')) {
throw new Error('Invalid state');
}
const codeVerifier = sessionStorage.getItem('code_verifier');
// Exchange code for tokens
const response = await fetch(`${AUTHORITY_URL}/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: codeVerifier
})
});
const tokens = await response.json();
// Get user info
const userResponse = await fetch(`${AUTHORITY_URL}/userinfo`, {
headers: { 'Authorization': `Bearer ${tokens.access_token}` }
});
setUser(await userResponse.json());
// Clean up
sessionStorage.removeItem('code_verifier');
sessionStorage.removeItem('state');
window.history.replaceState({}, '', '/');
}
return (
<div>
{user ? (
<p>Welcome, {user.name}!</p>
) : (
<button onClick={login}>Login</button>
)}
</div>
);
}