Open Charging Technology · Whitepapers

Open Charging Technology TOTP — HTTP Authentication

Version 1.0 — Draft, 2026-08-22 CC-BY-SA 4.0 · OpenChargingTechnology/Whitepapers · conformance suite: OpenChargingCloud/TOTPConformanceTests

This document specifies how the tokens of the Open Charging Technology TOTP Token Format are carried in HTTP for machine-to-machine authentication — as a drop-in replacement for HTTP Basic Authentication and for long-lived static bearer tokens (e.g. OCPI-style Authorization: Token …). Three bindings are defined:

The native bindings are implemented by Vanaheimr Hermod; Appendix A records the implementation status. Unlike the token format itself — frozen by deployed verifiers and finalized — this binding document is a draft: it has a single implementation, and its wire details may still change.

1. Introduction

HTTP Basic Authentication ships a static password with every request: one captured request, one log line, one misconfigured proxy, and the credential is compromised until somebody rotates it. Replacing the password with a TOTP bounds that damage to the token's validity time (30 seconds by default), and adding TLS channel binding (section 6.2) removes even that window: a bound token is useless outside the TLS session it was derived for.

The tokens themselves — derivation, parameters, validation rules, the previous/current/next acceptance window — are defined in the token format specification and are not redefined here. This document only defines how they travel in HTTP requests and how HTTP servers verify them.

Typical deployments: OCPP charging station WebSocket connections (the HTTP Upgrade handshake, section 7, today mostly protected by Basic Auth), OCPI peer connections, and internal service-to-service APIs.

2. Conventions

The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.

Token format spec refers to totp-token-format.md. The login is the name under which the verifier looks up a peer's TOTP configuration (shared secret, validity time, length, alphabet, hash algorithm — the TOTPConfig of token format spec Appendix B). It is deliberately not called a "user": a login names a role — a charging station, a backend service, a tenant — and nothing about it implies a natural person. Base64 is the Base64 encoding of RFC 4648 section 4, with padding.

3. Prerequisites

Client and verifier share a TOTP configuration per login, provisioned out of band. The verifier maintains a mapping login → TOTP configuration; the client knows its own login and configuration. How the mapping is provisioned and rotated is out of scope (token format spec section 8 states the requirements on the secret itself).

4. TOTP over HTTP Basic Authentication

The simplest deployment needs no new wire format at all: the client puts the current token into Basic's password field, exactly as defined by RFC 7617:

credentials  = "Basic" SP base64( login ":" totp )

Example — login chargingstation-0001, token CN63y502maVh (the defaults-mid-slot vector of the canonical test vectors):

GET /ocpp HTTP/1.1
Host: csms.example.com
Authorization: Basic Y2hhcmdpbmdzdGF0aW9uLTAwMDE6Q042M3k1MDJtYVZo

The verifier decodes the credentials per RFC 7617 and verifies the password as a presented token — the procedure of section 5.3, steps 2–3, unchanged.

What this buys. The "password" now rotates itself every validity interval: an observed or logged credential dies within the acceptance window instead of living until somebody remembers to rotate it. On the client this is often a zero-code change — every HTTP stack, proxy, load balancer and CLI on earth already speaks Basic, and many devices can be pointed at a rotating password without touching their firmware. Only the server's password comparison changes.

What it cannot do — Basic is traditional, and it shows:

Deployments that control both ends SHOULD therefore use the native scheme of section 5; the Basic binding is the zero-friction migration path, and a verifier MAY accept both during a transition.

5. The Authorization: TOTP scheme

5.1. Syntax

The credentials are a list of auth-params (RFC 9110 section 11.3), exactly as that specification recommends for new schemes:

credentials  = "TOTP" 1*SP #auth-param

; defined parameters:
;   login = <Base64 of the UTF-8 encoded login>          (MANDATORY)
;   totp  = <Base64 of the UTF-8 encoded TOTP>           (MANDATORY)
;   tlscb = "true" / "false"                             (OPTIONAL, default: true)

Example — login chargingstation-0001, raw token CN63y502maVh (the defaults-mid-slot vector of the canonical test vectors):

GET /ocpp HTTP/1.1
Host: csms.example.com
Authorization: TOTP login="Y2hhcmdpbmdzdGF0aW9uLTAwMDE=", totp="Q042M3k1MDJtYVZo", tlscb=false

The same login with a TLS-channel-bound token (gAzxPfYtmRgd, the tls-binding-sha256 vector) — the canonical form omits tlscb:

Authorization: TOTP login="Y2hhcmdpbmdzdGF0aW9uLTAwMDE=", totp="Z0F6eFBmWXRtUmdk"

Unlike Basic Authentication — which Base64-encodes login:password as one string and therefore cannot represent a ":" inside the login (RFC 7617 section 2) — login and token are encoded separately here, so both may contain any Unicode character, including ":".

5.2. Client behaviour

For every request the client computes the current token for its configuration (token format spec section 4) and sends it as above. A token MAY be reused for consecutive requests within its remaining validity time; clients MUST NOT send a cached token beyond that.

5.3. Server verification

On receiving the header, the verifier:

  1. Parses the auth-params: unknown parameters are ignored; duplicate parameters, a missing login or totp, invalid Base64 and an invalid tlscb value are rejected as malformed (section 8). login and totp are Base64-decoded and UTF-8-decoded; tlscb defaults to true when absent.
  2. Looks up the TOTP configuration for the login. An unknown login is rejected exactly like a wrong token (section 8).
  3. Computes previous, current and next for its own clock and the login's configuration — with tlscb=true (explicit or by default) the exporter material of the receiving TLS session is applied on the verifier's side as well, under the rules of section 6.2 — and accepts iff the presented token equals one of the three: the acceptance window of token format spec section 7, including its guidance: constant-time comparison, no widening of the window, single-use enforcement and rate limiting where replay matters.

Requests without an Authorization header, and requests that fail verification, are answered with 401 Unauthorized; the response SHOULD carry a challenge:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: TOTP realm="ocpp"

The realm parameter is OPTIONAL and follows RFC 9110 section 11.

6. The TOTP request header

6.1. Syntax

For deployments where the peer's login is already established — a mutual TLS connection, a per-client URL path, an already-authenticated session — the token travels in a dedicated request header, prefixed with a one-digit type. This binding descends from the proprietary Token-style authentication that GitHub's REST API originally defined (Authorization: token <token>) and that e-mobility protocols such as OCPI later adopted (Authorization: Token <static-token>): the same lightweight shape, except that the token now exchanges itself every validity interval instead of being a static, long-lived secret.

TOTP-header  = "TOTP:" OWS totp-type SP totp-value OWS
totp-type    = "0" / "1"
totp-value   = 1*VCHAR   ; a token of the token format spec
Type Meaning
0 A raw token (token format spec section 4).
1 A token bound to this TLS session (token format spec section 6).

Examples — the defaults-mid-slot and tls-binding-sha256 vectors:

TOTP: 0 CN63y502maVh
TOTP: 1 gAzxPfYtmRgd

Verifiers MUST reject unknown type digits.

6.2. TLS v1.3 channel binding

A bound token is derived with TLS exporter material per token format spec section 6 (label EXPORTER-Time-Based-One-Time-Password-v1, empty context, 32 bytes RECOMMENDED). The verifier derives the same material from its own side of the TLS connection the request arrived on, and verifies against tokens computed with it. A captured bound token is useless on any other connection, which makes replay impractical even if the token leaks into a log.

Channel binding MUST only be used on TLS v1.3 (or newer) connections — earlier TLS versions have no exporter interface with these properties. A verifier that cannot derive exporter material for the receiving connection MUST reject a bound token; it MUST NOT fall back to verifying it as raw.

These rules apply to channel binding in either native binding: the TOTP request header signals it with type digit 1, the Authorization: TOTP scheme with tlscb absent or true (section 5.1).

6.3. Verification

As in section 5.3, steps 2–3, with the login taken from the connection context instead of the header, and — for type 1 — the exporter material of the receiving TLS session applied to the token derivation on the verifier's side as well.

6.4. Caching

Generic HTTP caches do not know that the TOTP header carries a credential: the request looks unauthenticated to them, so a shared cache could store a response to an authenticated request and serve it to an unauthenticated client. Servers authenticating via the TOTP request header MUST therefore mark such responses Cache-Control: private (or no-store). Vary: TOTP is NOT a substitute — with tokens changing every validity interval it merely fragments the cache without expressing that the header is a credential. (The Authorization-based bindings of sections 4 and 5 do not need this rule: RFC 9111 section 3.5 already restricts caching of responses to requests with an Authorization header field.)

7. WebSocket handshakes

All three bindings apply unchanged to the HTTP Upgrade request of a WebSocket handshake (RFC 6455) — the primary use in OCPP, where a charging station's WebSocket connection is today typically protected by HTTP Basic Auth. Verification happens once, during the handshake; an accepted connection stays authenticated for its lifetime. Deployments needing periodic re-authentication of long-lived connections re-establish the connection or use an application-level mechanism; in OCPP the TOTP then also plays its other role, in dynamic QR codes (token format spec section 1.2).

8. Error handling

9. Protocol considerations (RFC 9110 §16.4.2)

RFC 9110 section 16.4.2 lists considerations for new authentication schemes. This scheme is private-use (see the third point), but answers them anyway:

10. Security considerations

11. Test vectors (normative for the native scheme)

The Authorization: TOTP scheme is covered by canonical vectors in the normative annex: test-vectors/totp-http-auth-vectors.json, gated as a whole file by the capability httpAuthentication:

Harnesses of implementations without the capability skip the file declaratively (a visible skip, never silence). The token values inside come from the canonical token vectors (defaults-mid-slot, tls-binding-sha256, …).

The Basic binding (section 4) gets no vectors of its own: its encoding is RFC 7617's, not ours, and the tokens are the token format's. The TOTP request header's value is a bare token (its wire format is trivial); its channel binding semantics are exercised through the scheme vectors.

12. References

Appendix A — Implementation status (informative)

Piece Hermod TOTP.ts
Token derivation yes yes
TOTP over Basic Authentication (section 4) — existing RFC 7617 machinery; only the server-side password check changes wire: yes, verification: pending no
Authorization: TOTP — build & parse as auth-params: login, totp, tlscb with its true-default (HTTPTOTPAuthentication, vector-driven tests) yes no
TOTP request header — build & parse (TOTPHTTPHeader), auto-attached by the HTTP client from its TOTPConfig yes no
TLS exporter material (channel binding derivation) yes no
Server-side verification against the login → config mapping (AWebSocketServer.ClientTOTPConfig) prepared, in progress no

The server-side verification procedure of sections 5.3 and 6.3 is what this document exists to pin down before that code lands.