Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-auth-oidc/Cargo.toml

[package]
name = "nornir-auth-oidc"
version = "0.2.0"
edition = "2021"
authors = ["Ignalina ApS"]
description = "OIDC / OAuth2 auth PROVIDER for the nordisk servers — validate a bearer JWT OFFLINE against a cached/pre-provisioned JWKS (airgap), fetched from jwks_uri (online) — a JWT that does not verify is REFUSED, never sent to /userinfo — with an opt-in opaque-token check (introspection at the discovered endpoint, bound to the audience), plus the device-authorization-grant (RFC 8628) flow for interactive CLI login. Covers Entra ID / Okta / Auth0 / Google / Keycloak / Zitadel through the one protocol. Implements the shared `nornir_service::AuthProvider` seam. Feature-gated sub-crate: the jsonwebtoken/JWKS closure is linked ONLY when a tier pins `oidc` (the embedded/mini bearer-only tier links none of it). Consumed by holger (reference impl), korp, dwarves + the shared `nornir-auth-client` login."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/nordisk/edda"
keywords = ["auth", "oidc", "oauth2", "jwt", "device-code"]
categories = ["authentication", "network-programming"]

[lib]
name = "nornir_auth_oidc"

[features]
default = []
# `online` — compile the outbound-HTTP paths (the reqwest `/userinfo` +
# `jwks_uri` fetch + the device-code token poll / refresh). OMITTED by the airgap
# tier so NO HTTP-egress client is linked; the offline JWT-against-JWKS path still
# works and an opaque / unresolvable-JWKS token then fails closed.
online = ["dep:reqwest", "dep:rustls", "dep:webpki-root-certs"]
# `test-support` — expose the throwaway-RSA test-JWKS + JWT-minting helpers as a
# `pub mod test_support` so a DOWNSTREAM crate's tests (holger-server-lib's gRPC
# promotion tests) can mint a signed admin/writer token verified offline. Off by
# default so no test keypair rides in a release build.
# It also serves the fake OpenID provider over https from a throwaway CA (`fake_op`):
# rustls (ring) for the server, rcgen for the CA and the certificate.
test-support = ["dep:rustls", "dep:rcgen"]

[dependencies]
nornir-service = { version = "0.1.3", path = "../nornir-service", features = ["auth-provider"] }
tonic = { version = "0.14", default-features = false }
# Offline / airgap OIDC: verify a bearer JWT's signature + standard claims
# (exp/iss/aud) against a cached or pre-provisioned JWKS with ZERO network — pure
# crypto over `ring`. Built-in `jwk` support (JwkSet / DecodingKey::from_jwk) is
# what lets an operator pre-provision keys as JSON. This is the heavy closure the
# bearer-only tier must NOT link — hence its own sub-crate.
#
# 9 -> 11, 2026-10-02 (W3 crypto bump): jsonwebtoken 10+ compiles no crypto
# backend by default — one must be chosen. `aws_lc_rs` since 2026-10-22: its
# `rust_crypto` backend links the PREVIOUS RustCrypto generation (rsa 0.9 / p256
# 0.13 / ed25519-dalek 2 / sha2 0.10, ~25 crates) beside the current one russh
# needs, while aws-lc-rs is already in every consumer's tree (rustls, russh).
# Verification only here; signing is test-only.
jsonwebtoken = { version = "11", default-features = false, features = ["use_pem", "aws_lc_rs"] }
# Digest the bearer token for the `/userinfo` result-cache key so the cache holds
# no raw secret and keys can't collide into an auth bypass.
blake3 = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4"
# The relying party (`rp`): base64url for state/nonce/PKCE and the Basic header,
# SHA-256 for the S256 challenge. The versions every consumer already links.
base64 = "0.22"
sha2 = "0.11"
# Online only: the pooled HTTP client for `/userinfo`, `jwks_uri`, and the
# device-code token endpoint. rustls (no OpenSSL) + json, matching the servers.
reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json", "form"], optional = true }
# reqwest 0.13 dropped its bundled roots and its `rustls` feature means aws-lc-rs (C)
# + the OS trust store. 0.12's `rustls-tls` was ring + the Mozilla bundle, so take
# `rustls-no-provider` and supply both here (see `tls_roots`). 2026-10-02.
rustls = { version = "0.23", default-features = false, features = ["ring", "std"], optional = true }
webpki-root-certs = { version = "1", optional = true }
# test-support only: the https fake's throwaway CA and certificate (`fake_op::FakeOp::start_https`).
rcgen = { version = "0.14", default-features = false, features = ["crypto", "pem", "ring"], optional = true }

[dev-dependencies]
# `fake_op` is compiled for this crate's own tests too (cfg(test)): its https half's deps.
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
rcgen = { version = "0.14", default-features = false, features = ["crypto", "pem", "ring"] }
tokio = { version = "1", features = ["macros", "rt", "io-util", "net"] }
tempfile = "3"