Simple Auth Protocol
Reference implementation in this repo: cme-authentication-mock.
1. Purpose and Scope
This document defines a simplified authentication protocol inspired by OpenID 1.0.
Design constraints:
- One fixed provider hostname (CME).
- No OpenID discovery.
- No Diffie-Hellman association exchange.
- CME and Relying Parties (RPs) share one secret delivered out-of-band (for example on paper).
- The protocol does not address user roles and permissions; it only handles authentication.
This is intentionally not full OpenID interoperability. It is a constrained profile for closed deployments.
2. Roles
- CME (Provider): central login server with fixed hostname.
- RP (Relying Party): application server that delegates user authentication to CME.
- User Agent: browser.
Simple interaction overview:

3. Static Configuration
Each RP and CME must be configured with:
provider_endpoint: fixed CME URL, for examplehttps://knihy.90.cz/authentication.shared_secret: high-entropy key exchanged offline (minimum 32 random bytes, base64 encoded in config).allowed_return_to: exact RP callback URL(s), for examplehttps://shift-planner.mathbox.90.cz/callbackclock_skew_seconds: default120.nonce_ttl_seconds: default600.
4. Protocol Messages
All messages are sent indirectly through browser redirects using HTTP 302 responses.
4.1 Authentication Request (RP → CME)
RP redirects browser to provider_endpoint with query parameters:
mode=checkid_setupreturn_to=<exact RP callback URL>rp_nonce=<RP-generated random nonce>op_ts=<unix timestamp seconds generated by RP>sig=<Base64(HMAC-SHA256(shared_secret, signing_input))>
Signing input is built in this exact order: mode, return_to, op_ts and rp_nonce. CME selects identity after successful user authentication. Nonce value should be a random UUID, preferably UUIDv4.
Accepted nonces must be stored on the CME side for the duration defined by nonce_ttl_seconds.
4.2 Positive Authentication Response (CME → RP)
After user login and consent, CME redirects browser to return_to with:
mode=id_resuseremail=<user email>(optional)username=<user name>(optional)userid=<user id>return_to=<same value as request>rp_nonce=<echo from request>op_ts=<unix timestamp seconds generated by CME>sig=<Base64(HMAC-SHA256(shared_secret, signing_input))>
Signing input is built in this exact order: mode, useremail, username, userid, return_to, rp_nonce, op_ts. If useremail or username is omitted, RP and CME must treat that field as an empty string when rebuilding the signed payload.
4.3 Negative Response (CME -> RP)
It's not necessary to implement it
If authentication is denied or canceled:
mode=cancel
No signature is required for cancel responses.
4.4 Diagram

5. Validation Rules
5.1 Authentication Request Validation (at CME)
CME accepts an authentication request only if all checks pass:
modeequalscheckid_setup.- All required fields are present:
mode,return_to,rp_nonce,op_ts,sig. return_toexactly matches an allowlisted callback URL for the requesting RP.rp_nonceformat is valid (recommended: UUIDv4).op_tsis within allowed clock skew.- Recomputed HMAC-SHA256 signature over
mode, return_to, op_ts, rp_nonceexactly matchessig(constant-time compare).
If any check fails, reject the request and log the reason.
5.2 Positive Authentication Response Validation (at RP)
Before accepting identity from id_res, RP must validate:
modeequalsid_res.- All required fields are present:
mode,userid,return_to,rp_nonce,op_ts,sig.useremailandusernameare optional and may be empty. return_toexactly matches the callback URL that is currently handling the response and an allowlisted RP callback URL.rp_nonceexists, matches the nonce stored for this login attempt, and has not been used before.op_tsis within allowed clock skew.- Recomputed HMAC-SHA256 signature over
mode, useremail, username, userid, return_to, rp_nonce, op_tsexactly matchessig(constant-time compare). - User mapping/authorization policy is satisfied before creating RP session.
If any check fails, reject authentication, clear one-time state as needed, and log the reason.
5.3 Cancel Response Handling
When RP receives mode=cancel, authentication must be treated as failed or canceled.
- Do not create RP session.
- Invalidate pending login state (nonce/session binding) for that attempt.
- Show a user-facing canceled/failed login message.
No signature verification is required for mode=cancel.
6. Security Requirements
- Use HTTPS for all CME and RP endpoints.
- Shared secret must be random and never sent in protocol messages.
- Rotate
shared_secretperiodically (for example every 90 days). - Keep previous secret during grace period to avoid hard cutover failures.
- Store used nonces for at least
nonce_ttl_secondsto prevent replay. - Limit and monitor failed signature checks.
- Use constant-time comparison for signatures.
- Keep server clocks synchronized (NTP).
6.1 Message Signing
This profile uses deterministic signing with a shared secret.
- The message includes
sig:Base64(HMAC-SHA256(shared_secret, token_contents)). token_contentsis built by serializing key-value lines in protocol-defined order:- Request (
mode=checkid_setup):mode,return_to,op_ts,rp_nonce - Positive response (
mode=id_res):mode,useremail,username,userid,return_to,rp_nonce,op_ts shared_secretMUST NOT be sent in request/response parameters and MUST NOT appear intoken_contents.- Key-value line format:
field_name:field_value\n
Rules for token_contents serialization are aligned with OpenID key-value form:
- No spaces before or after
:. - Use Unix newline (
\n, ASCII 10). - Include newline after every line.
- Use UTF-8 encoding.
- Optional response fields
useremailandusernamemust still occupy their signed position; when absent, serialize them as empty values.
Example token contents for positive response:
mode:id_res
useremail:<value or empty>
username:<value or empty>
userid:<value>
return_to:<value>
rp_nonce:<value>
op_ts:<value>
RP verification MUST rebuild token_contents from received fields in protocol-defined order and compare HMAC in constant time.
6.2 Security Risks and Mitigations
- Man-in-the-middle (MITM): An attacker intercepts traffic between browser, RP, and CME.
Mitigation: Enforce HTTPS/TLS everywhere, enable HSTS, and reject mixed-content deployments. - Message tampering: An attacker modifies
id_resfields in transit.
Mitigation: Verifysigover the fixed field order and use constant-time signature comparison. - Replay attacks: A previously valid signed response is reused later.
Mitigation: Validateop_tsfreshness and store used nonces fornonce_ttl_seconds. - Return URL abuse / open redirect: Attacker tries to force callbacks to an untrusted URL.
Mitigation: CME must strictly allowlistreturn_to; RP must exact-match callback URL on receipt. - Shared secret leakage: Secret is exposed via logs, config mistakes, or backups.
Mitigation: Store in a secret manager, never log secret material, restrict access, and rotate secrets regularly. - Weak nonce generation: Predictable
rp_nonceenables replay or request correlation attacks.
Mitigation: Generate nonces with a cryptographically secure RNG and sufficient entropy. - Clock manipulation / skew issues: Incorrect system time weakens
op_tsfreshness checks.
Mitigation: Use NTP, enforce bounded clock skew, and alert on significant clock drift. - Login CSRF / session confusion at RP: Browser is redirected with a valid response bound to the wrong local session.
Mitigation: Bindrp_nonceto RP session state and invalidate it immediately after use. - Provider impersonation: Client is redirected to a fake provider endpoint.
Mitigation: Pin the fixed CME hostname in configuration and require valid TLS certificate checks.
7. Minimal Example
7.1 RP → CME
GET https://knihy.90.cz/authentication?
mode=checkid_setup&
return_to=https%3A%2F%2Fshift-planner.mathbox.90.cz%2Fcallback&
op_ts=1772518394&
rp_nonce=6f7b6b5f9a2c4d5f&
sig=iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs=
7.2 CME → RP
GET https://shift-planner.mathbox.90.cz/callback?
mode=id_res&
useremail=jan.jirout%40internet-handel.cz&
username=Honza&
userid=24234&
return_to=https%3A%2F%2Fshift-planner.mathbox.90.cz%2Fcallback&
rp_nonce=6f7b6b5f9a2c4d5f&
op_ts=1772525600&
sig=iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs=
7.3 CME Negative Response (Cancel)
GET <RP app url>/auth/openid/callback?
mode=cancel
No signature is required for cancel responses.
8. Implementation Checklist
8.1 Provider
- Fixed endpoint and RP allowlists configured.
- Shared secret loaded securely.
- Request validation implemented.
- Assertion signing implemented with deterministic field order.
- Audit logging (request id, RP id, decision, reason).
8.2 Relying Party
- Nonce/state storage implemented.
- Callback exact URL validation implemented.
- Signature verification implemented.
- Replay and timestamp checks implemented.
- Local user session issuance only after full validation.