SolID is an open-source SDK for building an OIDC / OAuth 2.0 authorization server in Go. Security and privacy by design. Protocol logic is decoupled from the wire.
What this is. A modular SDK for assembling authorization servers. It covers five use cases out of the box. Every flow ships with a hardened, standards-based configuration. You pick the use case. The security posture comes with it. PAR, JAR, JARM, DPoP, PKCE, and sender-constrained tokens are mandatory, not options.
Who builds it. A security engineer specialized in identity, authentication, authorization, and privacy. In active development. Not a certified product.
| Use case | Flow | Enforcement |
|---|---|---|
| Online users | authorization_code |
Mandatory PKCE (S256) via PAR, JARM responses, state |
| Machine-to-machine | client_credentials |
Asymmetric client authentication only |
| Constrained devices (IoT) | ACE over CoAP | DTLS profile, CBOR tokens, device_code grant |
| Offline access | refresh_token |
Rotation on every use, grant-family revocation |
| Delegation / AI agents | token_exchange |
may_act gating, act chain depth cap; AI-agent CIMD clients are metadata-policy-governed (D-21) — see examples/agentclient |
OAuth and OIDC implementations fail more often than the protocols themselves. The specifications form a maze. Developers read them fast and interpret instead of apply. The result is faulty implementations in production.
These are real-world failures observed in the field:
- Teams skip the
authorization_codeflow because it does not carry user credentials. - Teams use
client_credentialsas a customer login, with the secret hard-coded in a public UI. - Applications authorize on token presence, without signature or content validation. "Token is here ⇒ you are admin."
- Authorization decisions read token claims that nobody verified.
Most OIDC providers offer a wide feature matrix. The deployer must understand every option to reach a secure configuration. Security posture becomes a function of OAuth expertise. One insecure client configuration can compromise the whole platform.
SolID takes the opposite position. It removes the insecure options instead of documenting them. The standards offer optional and recommended parameters. SolID promotes them to required. The deployer cannot misconfigure into a weak posture, because the weak posture does not exist in the code.
- Enforce OIDC features as one complete suite, selected by use case.
- Deliver security and privacy without full protocol expertise.
- Base the security posture on security objectives, not on protocol knowledge.
- Provide a battle-tested framework: adversarial tests, RFC conformance suites, and executable formal models (
spec/). - Decouple the protocol from the wire. OIDC binds to HTTP by design. SolID keeps protocol logic transport-agnostic, so the same services serve HTTP, gRPC, and CoAP.
- A complete, OIDC-compliant server. Optional and recommended parameters are promoted to required. SolID cannot pass the OIDC compliance test suite. This is deliberate.
- A turnkey product. It is an SDK. Assemblies live in
examples/.
The examples/ directory contains a reference authorization server and client integrations. See examples/README.md for how to run them.
Each example directory carries a README.md with a sequence diagram. The diagram describes the exact flow its main.go implements.
Each rule below is a design decision. The full record with evidence lives in docs/DESIGN_DECISIONS.md. Every entry cites the RFC section, the code anchor, and the test that proves it.
| Rule | Standard position | SolID position |
|---|---|---|
PAR + DPoP + JARM |
Optional extensions | Enforced for the authorization_code flow |
| Response types | Four registered types | code only. No implicit, no hybrid |
| PKCE | Public clients, plain allowed |
All clients, S256 only, constant-time compare |
| Client authentication | Secrets permitted | Asymmetric only: private_key_jwt, mTLS, attestation, SPIFFE |
| Dynamic CIMD clients | Draft: fetched document as-is | Operator metadata policy (Federation 1.0 §6.1.3.1 operators) applied before credential enforcement (D-21) |
| JOSE algorithms | Full registry | Elliptic curves + ML-DSA. No HS, RS, PS, none |
| Token content | Implementation choice | No PII. jti-addressed subject record, pairwise identifiers |
id_token in code flow |
Required by OIDC Core | Opt-in: SD-JWT id_token (RFC 9901), digests only, values in id_token_disclosures; not issued by default (D-12.1) |
| Refresh tokens | Rotation optional (BCP) or MAY (2.1) | Unconditional rotation, replay revokes the grant family |
audience |
Optional | Required whenever scope is requested |
| Authorization requests | Front-channel permitted | PAR with signed JWT request objects only |
| Client profiles | Registration metadata | Application-type profiles gate grants, response types, auth methods |
| Token serialization | JWT assumed | Assembler decision: reference token, JWT, HPKE-JWT, or CWT |
Web applications must use a server-side component to negotiate authorization. Client-side JavaScript must not see authorization artifacts before the user is identified.
Syntactic request validation is expressed as protovalidate annotations on the protobuf model (proto/oidc/**). Every service runs it as a first level. Semantic rules live in business logic.
Conformance detail with executed test proof lives in docs/COMPLIANCE_REPORT.md. Every RFC and draft below is vendored under docs/rfcs/ — see docs/rfcs/index.jsonl for the checksummed list.
| Standard | Notes |
|---|---|
| draft-ietf-oauth-v2-1-16 — OAuth 2.1 | Core requirements enforced, adversarially tested |
| RFC 9700 — Security BCP | Adversarially tested (integration/) |
| draft-ietf-oauth-security-topics-update-03 | AS-side audience hardening (§2.1), issuer-identifier audience (§2.1.2.1) |
| Standard | Notes |
|---|---|
| RFC 8414 — AS Metadata | |
| RFC 9207 — Issuer Identification | iss in every response mode |
| RFC 9728 — Protected Resource Metadata | |
| RFC 9470 — Step-Up Authentication | acr_values/max_age, unmet_authentication_requirements error |
| draft-ietf-oauth-client-id-metadata-document | Client ID Metadata Documents resolved at the AS behind the operator allow-list; OpenID Federation 1.0 §6.1.3.1 metadata-policy operators (value, add, default, one_of, subset_of, superset_of, essential) applied between the identifier match and the credential enforcement (cimd.NewPolicyResolver); redirect URIs validated on the final document — the AI-agent examples run policy-governed |
| Standard | Notes |
|---|---|
| RFC 7636 — PKCE | S256 only, every client type, constant-time compare |
| RFC 9126 — PAR | Atomic single-use request_uri, client-bound |
| RFC 9101 — JAR | Signed request objects only |
| FAPI JARM | JWT response modes only, success and error |
| RFC 9396 — Rich Authorization Requests | Carried in JAR objects, subset narrowing at the token endpoint, fail-closed |
| RFC 8707 — Resource Indicators | Registry-resolved URNs, unknown target rejected |
| Standard | Notes |
|---|---|
authorization_code |
code only. PKCE, PAR, JAR, JARM all mandatory |
refresh_token |
Unconditional rotation, replay revokes the grant family |
client_credentials |
Asymmetric client authentication only |
| RFC 8628 — Device grant | RFC 10027 hardening: one-time codes, poll throttle, no refresh tokens |
| CIBA Core | Poll mode, binding_message required, no refresh tokens (RFC 9700 §4.12.2) |
| RFC 8693 — Token Exchange | may_act gating, act chain depth cap 3 (fail-closed), cnf carried over |
| draft-ietf-oauth-identity-chaining-17 | Identity-chained tokens via token exchange, pairwise subjects |
| draft-ietf-oauth-identity-assertion-authz-grant-04 — ID-JAG | Issuance at the IdP AS, redemption at the Resource AS (sdk/idjag) |
| RFC 7523 §4 — JWT Bearer grant | Redeems ID-JAG assertions at the Resource AS |
| Standard | Notes |
|---|---|
| RFC 7521 / RFC 7523 — Assertion framework | |
private_key_jwt |
Algorithm allowlist checked before claims |
draft-ietf-oauth-attestation-based-client-auth-11 — attest_jwt_client_auth |
Header transport, registry-pinned attesters, jti-burn replay protection |
draft-ietf-oauth-spiffe-client-auth-02 — spiffe_jwt / spiffe_wit / spiffe_x509 |
All three SVID types, bundle-sourced trust only |
RFC 8705 §2.1 — tls_client_auth |
Exact-one registered subject binding, fail-closed |
RFC 8705 §2.2 — self_signed_tls_client_auth |
Registered JWKS x5c match, method-confusion guard |
| RFC 7591 — Dynamic Client Registration | No client secrets issued, code only, operator-gated |
| RFC 7592 — Registration Management | Registration access token is the sole credential |
| Standard | Notes |
|---|---|
| RFC 6750 — Bearer usage | Authorization header only. jkt-bound tokens rejected as Bearer |
| RFC 7800 — PoP key semantics | |
| RFC 9449 — DPoP | Nonce support included, jti burned after full validation |
| RFC 8705 §3 — Certificate-bound tokens | x5t#S256 confirmation, enforced at refresh |
| Standard | Notes |
|---|---|
| RFC 7519 — JWT | EC + ML-DSA allowlist at sign time and check time |
| RFC 8392 — CWT | COSE_Sign1, EC + ML-DSA only |
| draft-ietf-jose-hpke-encrypt-22 — HPKE with JWE | Integrated + Key Encryption modes |
| draft-ietf-cose-hpke-27 — HPKE with COSE | Shared ciphersuite registry with the JWE strategy |
| Reference tokens | Random string, signed UUID (verifiable) |
| Standard | Notes |
|---|---|
| Pairwise subject identifiers | Per-sector, salted |
| RFC 9901 — SD-JWT | Recursive disclosures, decoy digests, key binding mandatory |
| draft-ietf-spice-sd-cwt-08 — SD-CWT | COSE_Sign1 SD-CWT/KBT, definite-length and duplicate-key enforcement |
| Standard | Notes |
|---|---|
| RFC 7662 — Introspection | Opt-in disclosure: owner plus declared allowlist only |
| RFC 7009 — Revocation | Refresh revocation cascades to the grant family |
| Standard | Notes |
|---|---|
| RFC 9200 — ACE-OAuth | application/ace+cbor codec (sdk/ace), CoAP triangle demo |
| RFC 9201 — COSE profile | COSE_Key cnf members for PoP keys |
| RFC 9202 — DTLS profile | Mutual DTLS 1.2, certificate-bound tokens |
| RFC 8747 — CWT PoP semantics | cnf by value and by reference |
| Transport | Deployment | Where |
|---|---|---|
| HTTP | Shared building blocks (server/httpkit) |
Assembled by examples/authorizationserver |
| CoAP | Standalone ACE server, mutual DTLS 1.2 (RFC 9202 profile) | examples/coapace |
| AWS | Lambda | examples/ |
The per-decision design record lives in docs/DESIGN_DECISIONS.md. For every posture it states the relation to the governing standard (REPRESENTS / NEGATES / ENHANCES / HARDENS) and the override rules that settle standard conflicts. Per-standard conformance with executed test proof is in docs/COMPLIANCE_REPORT.md.
- OAuth 2.0 — the community site for the framework and its extensions
- Financial-grade API security profiles — read-only and read-write parts, the source of the JARM response-mode profile
- OAuth 2.0 for Browser-Based Apps — current guidance for SPAs and browser apps
- An Extensive Formal Security Analysis of the OpenID Financial-grade API — the academic basis for the front-channel hardening decisions
- PKCE vs. Nonce: Equivalent or Not? — why SolID enforces both