oidc
Generated by tinox doc
OpenID Connect login on top of oauth2's Authorization Code + PKCE flow: verifies the returned id_token's RS256 signature against the issuer's JWKS plus iss/aud/exp/nbf, and exposes the verified claims. OidcWebApp additionally wires a full turnkey /login + /callback browser login flow onto a plain HttpServer, with session state sealed into AES-256-GCM encrypted cookies (no server-side session store needed).
| Module | Version | Group |
|---|---|---|
crypto | 1.0.0 | tinox.core |
http_server | 1.0.0 | tinox.core |
json | 1.0.0 | tinox.core |
jwt | 1.0.0 | tinox.core |
oauth2 | 1.0.0 | tinox.core |
time | 1.0.0 | tinox.core |
Verify id token
import tinox.core.oidc;
class Main
{
fnc main() -> Int32
{
let client: OidcClient = OidcClient::new(
"https://accounts.google.com",
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"https://www.googleapis.com/oauth2/v3/certs",
"my-client-id",
"my-client-secret",
"https://myapp.example.com/callback"
);
let req: OAuth2AuthorizeRequest = client.buildAuthorizeUrl("openid email");
println("redirect user to: " + req.url);
// Callback handler, after the provider redirects back with ?code=...
let tokens: OidcTokenResponse = client.exchangeCode("the-code-from-callback", req.codeVerifier);
if tokens.ok
{
// id_token signature + iss/aud/exp/nbf already verified here
println("subject: " + tokens.claims["sub"].getString());
}
else
{
println("login failed: " + tokens.errorMessage);
}
return 0;
}
}
Turnkey web login
import tinox.core.oidc;
import tinox.core.http_server;
class Main
{
fnc main() -> Int32
{
let client: OidcClient = OidcClient::new(
"https://accounts.google.com",
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"https://www.googleapis.com/oauth2/v3/certs",
"my-client-id",
"my-client-secret",
"https://myapp.example.com/callback"
);
let webApp: OidcWebApp = OidcWebApp::new(client, "a-long-random-cookie-sealing-secret");
let server: HttpServer = HttpServer::new(8080);
webApp.install(server); // registers /login and /callback
server.get("/", ctx => {
if !webApp.requireLogin(ctx)
{
return; // already redirected to /login
}
let user: Map<String, JsonValue> = webApp.currentUser(ctx);
ctx.response.text("Welcome, " + user["email"].getString());
});
server.listen();
return 0;
}
}
class OidcClient
Fields
issuer | String | |
jwksUri | String | |
clientId | String | |
oauth | OAuth2Client |
Methods
fn new(issuer: String, authorizeUrl: String, tokenUrl: String, jwksUri: String, clientId: String, clientSecret: String, redirectUri: String) → OidcClient
fn buildAuthorizeUrl(scope: String) → OAuth2AuthorizeRequest
fn exchangeCode(code: String, codeVerifier: String) → OidcTokenResponse
fn refresh(refreshToken: String) → OidcTokenResponse
fn verifyAndWrap(tr: OAuth2TokenResponse) → OidcTokenResponse
fn verifyIdToken(idToken: String) → Map<String, JsonValue>
class OidcTokenResponse
Fields
accessToken | String | |
refreshToken | String | |
tokenType | String | |
expiresIn | Int64 | |
idToken | String | |
claims | Map<String, JsonValue> | |
ok | Bool | |
errorMessage | String |
class OidcWebApp
Fields
client | OidcClient | |
cookieSecret | String | |
loginPath | String | |
callbackPath | String | |
sessionCookieName | String | |
pendingCookieName | String |
Methods
fn new(client: OidcClient, cookieSecret: String) → OidcWebApp
fn install(server: HttpServer) → Nothing
fn requireLogin(ctx: HttpContext) → Bool
fn currentUser(ctx: HttpContext) → Map<String, JsonValue>
fn logout(ctx: HttpContext) → Nothing
fnc sealJson(data: Map<String, JsonValue>, secret: String) → String
fnc unsealJson(sealed: String, secret: String) → Map<String, JsonValue>
fnc readCookie(ctx: HttpContext, name: String) → String