โ† all docs

Inji Certify (Pre-Auth) โ€” direct-to-PDF implementation guide

Version: Inji Certify v0.14.0 (injistack/inji-certify-with-plugins:0.14.0);
inji-preauth-proxy v0.1.0 (adammwaniki/inji-preauth-poc:v0.1.0, built from
cmd/inji-preauth-proxy/).
Adapter type: inji_certify_preauth โ†’ internal/adapters/injicertify.
Role: issuer only (no holder wallet โ€” the PDF is the credential).

The pre-auth path is the "registry-as-record / paper QR" model: an operator enters (or CSV-loads)
claims, verifiably runs the whole OID4VCI pre-authorized_code dance server-side, and hands back
a printable PDF whose QR embeds a signed VC. The subject walks away with paper, not a wallet
credential. It is a completely separate stack from
Inji Certify Auth-Code โ€” its own containers, DB, DID, and public host,
so the two issuers' keys never cross.


1. What it is

A second Inji Certify instance running the csvdp-farmer (CSV/pre-auth data-provider) profile.
Certify itself is its own OAuth authorization server here (it mints the pre-auth token โ€” no
eSignet). verifiably drives it, encodes the resulting VC as a MOSIP PixelPass QR, and renders a
one-page PDF.

Formats: ldp_vc (Ed25519Signature2020). DirectPDF: true.


2. The role verifiably plays โ€” server-side OID4VCI client + PDF renderer

verifiably runs the entire pre-auth dance in internal/adapters/injicertify/pdf.go
(issueAsPDFPreAuth) โ€” the subject types nothing and holds no wallet:

  1. POST /v1/certify/pre-authorized-data (stage the offer with the claims).
  2. Extract the pre-authorized code from the offer JSON.
  3. POST /v1/certify/oauth/token (grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code)
    โ†’ access token + c_nonce.
  4. Build an adapter-held ES256 proof JWT (P-256, header {typ,alg,jwk}, no kid/iss per
    OID4VCI ยง7.2.1.1 anonymous pre-auth; aud = the offer's credential_issuer).
  5. POST /v1/certify/issuance/credential โ†’ signed VC.
  6. encodePixelPass(vc) โ†’ base45(zlib(CBOR(json))); renderCredentialPDF lays out claims + a
    high-recovery QR; bytes stashed on the adapter, served at GET /issuer/issue/pdf/{id}.

verifiably also observes signing kids (injidid.Preauth.Remember) and serves the pre-auth
did.json patched with them, independent of the primary instance.


3. Services (compose)

Service Image Port Role
inji-preauth-proxy (container inji-certify-preauth) adammwaniki/inji-preauth-poc:v0.1.0 8094:8090 Transparent proxy at Inji's network identity โ€” see ยง7.
inji-certify-preauth-backend injistack/inji-certify-with-plugins:0.14.0 โ€” The Certify pre-auth issuer. Profile default,csvdp-farmer.
certify-preauth-nginx nginx:1.27 8095:80 Fronts the pre-auth backend; routes credential + well-known through verifiably-go (inji-proxy-preauth).
certify-preauth-postgres postgres:15 5436:5432 DB inji_certify (isolated from the auth-code DB).

The pre-auth data provider stashes claims in Certify's own cache during the
pre-authorized-data POST โ€” it does not use vc_subject or identity_registry.


4. Environment variables

inji-certify-preauth-backend (compose):

Var Value Meaning
active_profile_env default,csvdp-farmer Loads certify-default.properties + certify-csvdp-farmer-preauth.properties.
CERTIFY_ISSUER_DID did:web:${PREAUTH_DID_DOMAIN:-${ISSUER_DID_DOMAIN:-certify-preauth-nginx}} Pre-auth issuer DID, decoupled from the auth-code instance.
mosip_certify_domain_url ${PREAUTH_PUBLIC_URL:-http://inji-certify-preauth:8090} Public โ€” so credential_issuer/credential_endpoint in offers + the DID are externally resolvable.
MOSIP_CERTIFY_AUTHN_JWK_SET_URI ${PREAUTH_JWKS_URI:-http://inji-certify-preauth:8090/โ€ฆ/jwks.json} JWKS stays internal (cluster-local).
SPRING_DATASOURCE_URL jdbc:postgresql://certify-preauth-postgres:5432/inji_certify Isolated DB.

Derived in deploy.sh cmd_up: PREAUTH_PUBLIC_URL (url_for inji-certify-preauth, port 8094)
โ†’ PREAUTH_DID_DOMAIN (host part). Unset host โ‡’ certify-preauth-nginx (dev). These are
deliberately separate from AUTHCODE_PUBLIC_URL/ISSUER_DID_DOMAIN โ€” re-pointing the shared
var would move the auth-code issuer onto the pre-auth did.json (which carries only the pre-auth
key) and break auth-code verification.

Adapter config (backends.json): baseUrl / publicBaseUrl =
https://inji-certify-preauth.in-labs.cdpi.dev, internalBaseUrl / offerIssuerUrl =
http://inji-certify-preauth:8090.


5. Caddy

inji-certify-preauth.in-labs.cdpi.dev {
	reverse_proxy certify-preauth-nginx:80
}

A plain reverse proxy โ€” no did.json interception here (the pre-auth instance's did.json is served
through its own nginx โ†’ verifiably-go inji-proxy-preauth). Hairpin: caddy-public aliases
inji-certify-preauth.<domain> so the wallet-api (walt.id) can resolve a pre-auth
credential_offer_uri whose host is the public pre-auth name without hairpinning the box IP.


6. Runtime configs

  • deploy/compose/stack/inji/certify/certify-default.properties โ€” shared with auth-code; the
    pre-auth instance leaves AUTHCODE_ALLOWED_AUD unset, so authn.allowed-audiences keeps the
    ${mosip.certify.domain.url} default (certify's own token, aud = its public host).
  • deploy/compose/stack/inji/certify/certify-csvdp-farmer-preauth.properties โ€” the pre-auth /
    CSV data-provider plugin config.
  • deploy/compose/stack/inji/certify-preauth-nginx/nginx.conf โ€” same resolver+variable pattern as
    certify-nginx; routes the credential endpoint + well-known through
    verifiably-go/inji-proxy-preauth/*.
  • deploy/compose/stack/inji/certify/init-preauth.sql + init-preauth.sh โ€” seed the isolated DB.

7. The inji-preauth-proxy (why it exists)

Strict OID4VCI wallets (walt.id, Sphereon) follow the issuer identifier verbatim. Inji advertises
credential_issuer: http://inji-certify-preauth:8090 but serves metadata under
/v1/certify/issuance/.well-known/โ€ฆ, not at the root โ€” so strict wallets 404. The proxy
(cmd/inji-preauth-proxy/main.go) sits at Inji's network identity and:

  1. answers GET /.well-known/openid-credential-issuer at the root, proxying to Inji's real
    metadata URL and rewriting the payload (strips null optionals, empty credential_definition,
    walt.id-incompatible display) so strict JSON parsers accept it;
  2. forwards the credential request through.

This is what let SD-JWT โ†’ an external Credo mobile wallet work for pre-auth; walt.id's own
holder cannot claim pre-auth (v0.18.2 emits a non-conformant proof) โ€” use an external wallet + PDF.


8. Databases & APIs

Database inji_certify on certify-preauth-postgres (isolated). Uses credential_config
(custom schemas created via the issuer builder); not vc_subject/identity_registry (the
pre-auth data-provider caches claims itself).

Upstream API (via the proxy/nginx): /v1/certify/pre-authorized-data,
/v1/certify/oauth/token, /v1/certify/issuance/credential,
/v1/certify/issuance/.well-known/openid-credential-issuer.
verifiably surface: POST /issuer/issue (mode = "As a PDF"), GET /issuer/issue/pdf/{id},
plus the delegated-access pre-auth APIs /api/v1/delegation/inji/preauth/{issue,claim}.
Adapter files: internal/adapters/injicertify/pdf.go, pixelpass.go, export.go;
internal/handlers/pdf_download.go; internal/injidid/observed.go (Preauth).


9. Deploy + verify

ssh colombo 'cd /root/verifiably/verifiably-go && ./deploy.sh up inji'   # brings up both certify instances

inji-preauth-proxy is built from source on first up (image adammwaniki/inji-preauth-poc:v0.1.0).
Smoke: /issuer/issue with an Inji Pre-Auth DPG + "As a PDF" โ†’ a PDF downloads; rasterize the
QR and upload to Inji Verify โ†’ SUCCESS.


10. Gotchas

  • Keep it separate from auth-code. Distinct DB (certify-preauth-postgres), DID
    (did:web:inji-certify-preauth.<domain>), and did.json (only the pre-auth key). Never point
    ISSUER_DID_DOMAIN at the pre-auth host.
  • JWKS is internal on purpose. MOSIP_CERTIFY_AUTHN_JWK_SET_URI stays cluster-local; only the
    domain_url (issuer/offer URLs) goes public.
  • walt.id holder can't claim pre-auth. v0.18.2 emits a non-conformant proof (kid+jwk) and
    PUT /credentials is NotImplemented โ€” use an external wallet + SD-JWT, or the PDF path.

See also: Inji Certify Auth-Code ยท Inji Verify ยท
../architecture.md ยท ../deploy.md.

Source: docs/dpg/inji-certify-preauth.md