25 functions · OAuth 2.0 + OIDC helpers · MIT

xoauth2 Function Reference

OAuth 2.0 (RFC 6749) client: build.bat xoauth2 compiles it in. Authorization-code flow + PKCE (RFC 7636) + token refresh + five provider presets; OIDC helpers (one-shot nonce, HttpGet for JWKS, GetUserInfo). Transport is injected via xoauth2httpproc — mocks drive the whole flow offline, hosts can swap in any HTTP implementation, and an xrt net/tls/http1 convenience implementation ships along (http/https, IPv6, system or custom CA, 1 MB response cap). Data-couples with xjwt — zero linkage dependency: the OIDC login composes from eight lines of glue in the script layer.

Config & presets (7 functions)

xoauth2UseGithub / UseGoogle / UseWechat / UseMicrosoft / UseCustom presets

One-line setup: GitHub (includes api.github.com/user), Google (OIDC: issuer + JWKS + nonce), WeChat (OAuth only, PKCE off), Microsoft (endpoints and issuer generated per tenant — a third owned URL held by the client and freed by ClientUnit), Custom (your own xoauth2config). Presets carry provider knowledge; transport injection is the separate next step (Config.Http = ...).

void xoauth2UseGithub(xoauth2client* pClient, const char* id, const char* secret, const char* redirect);

xoauth2ConfigInit / xoauth2ClientUnit lifecycle

Zeroes the config (UsePkce on by default, AuthStyle auto); ClientUnit ends a session: zeroes the sensitive state/verifier/nonce, frees preset-owned endpoint URLs and clears the config — re-preset before reuse. A Microsoft tenant longer than 256 characters is rejected (endpoints left NULL).

Login flow (3 functions)

xoauth2BeginLogin authorize entry

Generates a random state (CSRF) and PKCE verifier/challenge (S256, honoring the preset's UsePkce), a nonce when UseNonce is set, and builds the authorize URL (scope/redirect fully form-encoded). Returns the URL (xrtFree) — redirect the user's browser there.

char* xoauth2BeginLogin(xoauth2client* pClient);

xoauth2CompleteLogin code exchange

Constant-time state compare (burned on match, replay-proof) → builds the token request (AuthStyle=BODY puts fully encoded credentials in the body; =BASIC sends the Authorization header) → burns the verifier → POST via the transport callback → graded response parsing. Returns a token on success (free with xoauth2TokenFree). After a state mismatch you must BeginLogin again.

xoauth2token* xoauth2CompleteLogin(xoauth2client* pClient, const char* sCode, const char* sState);
Example · GitHub login in three steps
xoauth2client oauth;
xoauth2UseGithub(&oauth, sClientId, sSecret, "https://app.example/cb");
oauth.Config.Http = xoauth2HttpXrt;          /* inject transport */
oauth.Config.HttpContext = pHttp;            /* handle from xoauth2HttpXrtCreate */

char* url = xoauth2BeginLogin(&oauth);      /* 302 to the authorize page */
/* ...user authorizes; callback arrives with code + state... */
xoauth2token* tok = xoauth2CompleteLogin(&oauth, sCode, sState);
if ( tok ) {
    /* tok->AccessToken / RefreshToken / ExpiresAt */
    xoauth2TokenFree(tok);                   /* sensitive fields already zeroed */
}

xoauth2TokenFree free

Frees the token — access/refresh/id token plaintext is zeroed before release.

Token management (2 functions)

xoauth2Refresh refresh

Builds the grant_type=refresh_token request, runs it through the transport and parses the graded response (AuthStyle as in CompleteLogin). The old token is the caller's to free. Four error levels: NETWORK (connect/timeout/TLS) → TOKEN_ENDPOINT (HTTP error status) → TOKEN_DENIED (provider error field) → TOKEN_RESPONSE (2xx body parse failure).

xoauth2token* xoauth2Refresh(xoauth2client* pClient, const char* sRefreshToken);

xoauth2TokenExpiring expiry check

Checks against ExpiresAt and the current clock whether the token expires within leewaySeconds. When the provider returns no expires_in (e.g. GitHub) it counts as not expiring — unknown ≠ about to expire, and such tokens usually carry no refresh_token either.

OIDC helpers (3 functions, data-coupling layer)

xoauth2HttpGet generic GET

Runs a GET through the client's transport callback for any URL, returning the response body text (xrtFree). Typical use: fetching the JWKS JSON. Error grading as in token exchange.

char* xoauth2HttpGet(xoauth2client* pClient, const char* sUrl, const char* sAuthHeader, int* piStatus);

xoauth2NonceConsume nonce consume

Constant-time compare against the nonce generated by BeginLogin; on match it is burned (one-shot, replay-proof) and true is returned; a mismatch sets XOAUTH2_ERROR_NONCE_MISMATCH.

xoauth2GetUserInfo userinfo

GETs UserInfoUrl with Authorization: Bearer (dynamically allocated — long tokens are never truncated) and returns the claims object (non-object JSON rejected). Also the right path for non-OIDC providers (e.g. WeChat /sns/userinfo).

xvalue* xoauth2GetUserInfo(xoauth2client* pClient, const char* sAccessToken);
Example · the eight-line glue with xjwt
xoauth2token* tok = xoauth2CompleteLogin(&oauth, code, state);

int st;
char* jwksJson = xoauth2HttpGet(&oauth, oauth.Config.JwksUrl, NULL, &st);
xjwtjwks* keys = xjwtJwksParse(jwksJson);        /* string in */
xjwtcheck ck;  xjwtCheckInit(&ck);
ck.Issuer = oauth.Config.Issuer;               /* preset knowledge, just a string */
ck.Audience = oauth.Config.ClientId;
xvalue* claims = xjwtVerifyJwks(tok->IdToken, keys, &ck);

char nonce[128];
xjwtClaimString(claims, "nonce", nonce, sizeof(nonce));
xoauth2NonceConsume(&oauth, nonce);            /* constant-time + burn */

Convenience transport (5 functions)

xoauth2HttpXrtInit / xoauth2HttpXrtUnit stack variant

Creates or borrows a net engine + system/custom CA verifier + timeout (default 15 s). For C hosts declaring on the stack.

xoauth2HttpXrtCreate / xoauth2HttpXrtDestroy heap variant

Heap-allocated handle — for when the opaque struct cannot be declared on the stack (the natural path for the script layer). Parameters as in Init.

xoauth2httpxrt* xoauth2HttpXrtCreate(void* pBorrowedEngine, const char* sCaPem, uint64_t uTimeoutUs);

xoauth2HttpXrt transport callback

POST/GET dual method (POST with form headers, GET without entity headers; Authorization passed through in both), http/https schemes, IPv6 literals, future-based TLS/TCP IO, xrt http1 assembly and parsing, 1 MB response cap. Attach on Config.Http.

Utilities & errors (5 functions)

xoauth2PkceGenerate / xoauth2StateGenerate / xoauth2UrlEncode offline tools

PKCE code_verifier (random 43 chars) + challenge (BASE64URL(SHA256(verifier)), reproducibly verifiable); random state (URL-safe); URL percent-encoding — all usable offline for tests and custom scenarios.

xoauth2LastError error read

Returns the xoauth2-domain error code (XOAUTH2_ERROR_*, eight values), 0 for other domains. Read immediately after a failure. The four levels map straight to HTTP semantics: STATE_MISMATCH→400, TOKEN_DENIED→401, ENDPOINT/RESPONSE→502, NETWORK→504.

Security baseline: constant-time one-shot state/nonce, verifier burned after use, fully form-encoded request bodies, Basic header per RFC 6749 §2.3.1, token fields zeroed before free, non-integer expires_in rejected. 166+15 tests (full mock paths, real loopback end-to-end, composition regression with xjwt). Full source at lib/xoauth2; data-couples with xjwt (no requires — the two libraries do not know each other).