134 lines
4.8 KiB
XML
134 lines
4.8 KiB
XML
import { CodeBlock } from "../components/CodeBlock";
|
|
import { Callout } from "../components/Callout";
|
|
|
|
export function AuthenticationPage() {
|
|
return (
|
|
<div>
|
|
<h1 id="authentication">Authentication</h1>
|
|
<p>
|
|
All <code>/v1/*</code> endpoints require a Bearer JWT in the{" "}
|
|
<code>Authorization</code> header. Veylant IA validates the token against Keycloak (OIDC)
|
|
or uses a mock verifier in development mode.
|
|
</p>
|
|
|
|
<h2 id="bearer-token">Bearer Token</h2>
|
|
<CodeBlock
|
|
language="bash"
|
|
code={`curl http://localhost:8090/v1/chat/completions \\
|
|
-H "Authorization: Bearer <your-access-token>" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hi"}]}'`}
|
|
/>
|
|
|
|
<h2 id="dev-mode">Development Mode</h2>
|
|
<Callout type="info" title="Development mode auth bypass">
|
|
When <code>server.env=development</code> and Keycloak is unreachable, the proxy uses a{" "}
|
|
<code>MockVerifier</code>. Any non-empty Bearer token is accepted. The authenticated user
|
|
is injected as <code>admin@veylant.dev</code> with <code>admin</code> role and tenant ID{" "}
|
|
<code>dev-tenant</code>.
|
|
</Callout>
|
|
<CodeBlock
|
|
language="bash"
|
|
code={`# Any string works as the token in dev mode
|
|
curl http://localhost:8090/v1/chat/completions \\
|
|
-H "Authorization: Bearer dev-token" \\
|
|
...`}
|
|
/>
|
|
|
|
<h2 id="keycloak-flow">Production: Keycloak OIDC Flow</h2>
|
|
<p>In production, clients obtain a token via the standard OIDC Authorization Code flow:</p>
|
|
<ol>
|
|
<li>Redirect user to Keycloak login page</li>
|
|
<li>User authenticates; Keycloak redirects back with an authorization code</li>
|
|
<li>Exchange code for tokens at the token endpoint</li>
|
|
<li>Use the <code>access_token</code> as the Bearer token</li>
|
|
</ol>
|
|
<CodeBlock
|
|
language="bash"
|
|
code={`# Token endpoint (replace values)
|
|
curl -X POST \\
|
|
http://localhost:8080/realms/veylant/protocol/openid-connect/token \\
|
|
-d "grant_type=password" \\
|
|
-d "client_id=veylant-proxy" \\
|
|
-d "username=admin@veylant.dev" \\
|
|
-d "password=admin123"
|
|
|
|
# Response includes:
|
|
{
|
|
"access_token": "eyJhbGci...",
|
|
"expires_in": 300,
|
|
"refresh_token": "eyJhbGci...",
|
|
"token_type": "Bearer"
|
|
}`}
|
|
/>
|
|
|
|
<h2 id="jwt-claims">JWT Claims</h2>
|
|
<p>The proxy extracts the following claims from the JWT:</p>
|
|
<div className="overflow-x-auto my-4">
|
|
<table className="w-full text-sm border rounded-lg overflow-hidden">
|
|
<thead>
|
|
<tr className="bg-muted/50 border-b">
|
|
<th className="text-left px-4 py-2.5 font-semibold">Claim</th>
|
|
<th className="text-left px-4 py-2.5 font-semibold">Source</th>
|
|
<th className="text-left px-4 py-2.5 font-semibold">Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{[
|
|
{ claim: "sub", source: "Standard JWT", desc: "User ID (UUID)" },
|
|
{ claim: "email", source: "Standard JWT", desc: "User email" },
|
|
{ claim: "realm_access.roles", source: "Keycloak extension", desc: "RBAC roles: admin, manager, user, auditor" },
|
|
{ claim: "veylant_tenant_id", source: "Keycloak mapper", desc: "Tenant UUID" },
|
|
{ claim: "department", source: "Keycloak user attribute", desc: "Department name for routing rules" },
|
|
].map((row) => (
|
|
<tr key={row.claim} className="border-b last:border-0">
|
|
<td className="px-4 py-2.5 font-mono text-xs">{row.claim}</td>
|
|
<td className="px-4 py-2.5 text-muted-foreground text-xs">{row.source}</td>
|
|
<td className="px-4 py-2.5 text-muted-foreground text-xs">{row.desc}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h2 id="test-users">Pre-configured Test Users</h2>
|
|
<p>The Keycloak realm export includes these users for testing:</p>
|
|
<CodeBlock
|
|
language="bash"
|
|
code={`# Admin user (full access)
|
|
username: admin@veylant.dev
|
|
password: admin123
|
|
roles: admin
|
|
|
|
# Regular user (restricted to allowed models)
|
|
username: user@veylant.dev
|
|
password: user123
|
|
roles: user`}
|
|
/>
|
|
|
|
<h2 id="error-responses">Auth Error Responses</h2>
|
|
<p>Authentication errors always return OpenAI-format JSON:</p>
|
|
<CodeBlock
|
|
language="json"
|
|
code={`// 401 — Missing or invalid token
|
|
{
|
|
"error": {
|
|
"type": "authentication_error",
|
|
"message": "invalid or missing authorization token",
|
|
"code": "invalid_api_key"
|
|
}
|
|
}
|
|
|
|
// 403 — Valid token, insufficient role
|
|
{
|
|
"error": {
|
|
"type": "permission_error",
|
|
"message": "role 'user' does not have access to model 'gpt-4o'. Allowed: gpt-4o-mini",
|
|
"code": "permission_denied"
|
|
}
|
|
}`}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|