This is the mostly-complete OpenID specification.
Optional fields default to the values given.
This page contains low-level details. For a high-level overview, see OpenID specs.
Document outline:
- Overview
- Limits
- Key-Value Colon/Newline format
Flow
- Common steps
- Flow for a stateless Consumer ("dumb mode")
- Flow for a stateful Consumer ("smart mode")
Association mode (smart mode)
- Association mode 1: Getting an HMAC secret, no Diffie-Hellman (DH) encryption
- Association mode 2: Getting an HMAC secret, encrypted with a DH shared secret
Checking identity
- Checking identity for computing-enabled Consumers (smart mode)
- Checking identity for Consumers in limited environments
- Message validation using stateless key
Server runtime errors, HTTP return codes
- References
Overview
The OpenID protocol is an exchange of messages which can operate in either stateless ("dumb mode") or stateful ("smart mode") form over an HTTP or HTTPs connection. All data sent back by Server in response to a Consumer HTTP request are transmitted via the body of the query, like in a regular HTTP request.
A protocol diagram summarising the OpenID protocol, has been kindly provided by Steven J. Murdoch.
The protocol is marked as "version 1.1" in the Perl reference implementation.
Limits
- Identity URL: 255 max bytes
- Identity server URL: 2047 max bytes (after consumer-added URL arguments, so keep the raw endpoint URL well below this)
- return_to URL: 2047 max bytes (after identity-server added URL arguments, so keep the raw return_to URL well below this)
- assoc_handle: 255 characters or less, and consist only of ASCII characters in the range 33-126 inclusive (i.e. printable non-whitespace characters).
Key-Value Colon/Newline format
Lines of:
some_key:Some value
- No space before or after the colon
- newline character Unix-style: just ASCII character 10 ("
\n") - newline at end of EACH line, not just between lines
- MIME type is unspecified, but probably just
text/plain. Consumers shouldn't break if they see something else, though, as the proper MIME type might change. - Character encoding is UTF-8, though in practice only ASCII characters will be used in the protocol. The only time non-ASCII characters would be used is in error responses (see below), but those are recommended to be in English.
Flow
Here is, shortly, the identification process flow which forks depending on whether immediate mode is used.
Common steps
(input sanity checks are omitted)
- a. User-Agent submits a claimed identity to Consumer.
- b. Consumer retrieves the OpenID Server and delegation parameters for the claimed URL.
Flow for a stateless Consumer ("dumb mode")
Consumer builds an "identity check" GET URL request from the OpenID Server URL (checkid_immediate), specifying a return URL. Consumer redirects the User-Agent to the Server.
If check failed, Server builds a "failed assertion" GET URL (id_res) from the Consumer-provided return URL, specifying a user setup URL. Server redirects the User-Agent to the Consumer. Consumer takes whatever action it considers appropriate, with the possibility of pointing the User-Agent to the user setup URL.
If check succeeded, Server builds a "succeeded assertion" GET URL (id_res) from the Consumer-provided return URL. Server redirects the User-Agent to the Consumer.
Validation step: Consumer requests message authentication (check_authentication via POST) with the stateless assoc_handle.
Flow for a stateful Consumer ("smart mode")
If server is not associated, Consumer requests a shared secret and an association handle. (associate via POST)
Consumer builds an "identity check" GET URL request from the OpenID Server URL (checkid_setup), specifying a return URL. Consumer redirects the User-Agent to the Server. Server proceeds to any necessary steps leading to allowing, denying or failing (not logged in) access to the URL.
If check failed because the user declined to share their identity, Server builds a "canceled notification" GET URL (cancel) from the Consumer-provided return URL. Server redirects the User-Agent to the Consumer. Consumer takes note of the process abortion.
If check failed because the user is not set up on the Server (e.g. not logged in), Server builds a "failed assertion" GET URL (id_res) from the Consumer-provided return URL. Server redirects the User-Agent to the Consumer. Consumer takes note of the process abortion and generally offers the option to use the Server-provided setup URL.
If check succeeded, Server builds a "succeeded assertion" GET URL (id_res) from the Consumer-provided return URL. Server redirects the User-Agent to the Consumer.
Consumer checks if its provided associate handle is valid.
If valid, Consumer checks messages signatures using the shared secret associated to the handle.
If not valid, Consumer invalidates the previous association and switches to dumb mode with the returned stateless assoc_handle, step "validation".
Association mode (smart mode)
This is the highly recommended mode to consume OpenIDs. Associated information may be reused for a given period of time (once per day/week/month per consumer per server, depending on expiries and server recommendation.)
Association mode 1: Getting an HMAC secret, no Diffie-Hellman (DH) encryption
In this mode, the shared secret is sent by Server as plain text, in Base64 encoding.
consumer --> idserver:
- POST OpenID server URL (Content-Type: application/x-www-form-urlencode with properly encoded request string as body and Content-Length set)
- 'openid.mode=associate'
- 'openid.assoc_type=HMAC-SHA1' (optional)
idserver --> consumer: (text/plain ASCII newline-terminated key:value pairs)
- 'assoc_type:HMAC-SHA1'
- 'assoc_handle:' + HMAC secret handle
- 'expires_in:' + association lifetime, in base-10 seconds
- 'mac_key:' + base64(secret(HMAC secret handle))
secret is the Server function producing a mac_key depending on which assoc_type is used.
Association mode 2: Getting an HMAC secret, encrypted with a DH shared secret
In this mode, the shared secret is sent encrypted with Diffie-Hellman, and encoded in Base64. If the Server does not support DH, they may ignore the DH fields in the request and reply exactly as to a non-DH request.
All integers are represented in big-endian signed two's complement, Base64 encoded. In other words, the "btwoc" function used below is a function that takes a bigint and returns its shortest big-endian two's complement notation.
The Consumer starts by choosing the group parameters:
- p (modulus)
- g (generator)
- x Consumer private key: 1 <= x < p-1
and produces a public key.
The Consumer public key is X = g ^ x mod p
consumer --> idserver:
- POST OpenID server URL (Content-Type: application/x-www-form-urlencode with properly encoded request string as body and Content-Length set)
- 'openid.mode=associate'
- 'openid.assoc_type=HMAC-SHA1' (optional)
- 'openid.session_type=DH-SHA1'
- 'openid.dh_modulus=' + base64(btwoc(p)) (optional)
- 'openid.dh_gen=' + base64(btwoc(g)) (optional)
- 'openid.dh_consumer_public=' + base64(btwoc(g ^ x mod p))
If the consumer does not supply either or both of dh_modulus or dh_gen, they default to these integer values:
dh_modulus: 15517289818147369747123225776371553991572480196691540447970779531405\
76293785419175806512274236981889937278161526466314385615958256881888\
89951272158842675419950341258706556549803580104870537681476726513255\
74704076585747929129157233451064324509471500722962109419434978392598\
4760375594985848253359305585439638443
dh_gen: 2
idserver --> consumer: (text/plain ASCII newline-terminated key:value pairs)
The server chooses the Server private key:
- 1 <= y < p-1
and then produces a public key.
The Server DH public key is Y = g ^ y mod p The shared DH secret is thus g ^ xy mod p = (g ^ x) ^ y mod p = (g ^ y) ^ x mod p
- 'assoc_type:HMAC-SHA1'
- 'assoc_handle:' + HMAC secret handle
- 'expires_in:' + association lifetime, in base-10 seconds
- 'session_type:DH-SHA1'
- 'dh_server_public:' + base64(btwoc(g ^ y mod p))
- 'enc_mac_key:' + base64(H(btwoc(g^ (xy) mod p)) XOR secret(HMAC secret handle))
The underlying mac_key must be the same length as the output of H, the hash function - in this instance, 160 bits (20 bytes) for SHA1.
Checking identity
Checking identity for computing-enabled Consumers (smart mode)
(Assuming association was correctly done.)
consumer redirect -> UA -> idserver:
- GET OpenID server URL
- 'openid.mode=checkid_immediate' (or checkid_setup)
- 'openid.identity=' + OpenID URL
- 'openid.assoc_handle=' + HMAC secret handle (optional but is set because association was made - if missing then entering "dumb mode")
- 'openid.return_to=' + return URL (with nonce)
- 'openid.trust_root=" + trust root (optional; see version 0 specs nihongo de)
If Server-side successful:
idserver redirect -> UA -> consumer:
- GET return URL (merged with the following)
- 'openid.mode=id_res'
- 'openid.identity=' + OpenID URL
- 'openid.assoc_handle=' + opaque handle (set to a new value if passed handle has expired; see openid.invalidate_handle below)
- 'openid.return_to=' + original return URL
- 'openid.signed=' + 'mode,identity,return_to'
- 'openid.sig=' + base64(HMAC( secret(assoc_handle), token_contents ))
- 'openid.invalidate_handle=' + handle (optional; is set if passed handle has expired)
token_contents in openid.sig are (each key:value pair is newline-terminated)
- 'mode:id_res'
- 'identity:' + identity OpenID URL
- 'return_to:' + return_to URL
Then Consumer validates message locally using its mac_key.
If Server-side failed (immediate or "setup+not-logged-in"):
idserver redirect -> UA -> consumer (failed):
- GET return URL (merged with the following)
- 'openid.mode=id_res'
- 'openid.user_setup_url=' + Server setup URL
If Server-side failed ("setup+user declined"):
idserver redirect -> UA -> consumer (cancelled):
- GET return URL (merged with the following)
- 'openid.mode=cancel'
Checking identity for Consumers in limited environments
(aka "dumb consumer mode")
consumer redirect -> UA -> idserver:
- GET OpenID server URL
- 'openid.mode=checkid_immediate' (or checkid_setup)
- 'openid.identity=' + OpenID URL
- 'openid.return_to=' + return URL (with nonce)
- 'openid.trust_root=" + trust root (optional; see version 0 specs nihongo de)
If Server-side successful:
idserver redirect -> UA -> consumer:
- GET return URL (merged with the following)
- 'openid.mode=id_res'
- 'openid.identity=' + OpenID URL
- 'openid.assoc_handle=' + opaque handle
- 'openid.return_to=' + original return URL
- 'openid.signed=' + 'mode,identity,return_to'
- 'openid.sig=' + base64(HMAC( secret(assoc_handle), token_contents ))
See above for recreating token_contents.
If Server-side failed (immediate or "setup+not-logged-in"):
idserver redirect -> UA -> consumer (failed):
- GET return URL (merged with the following)
- 'openid.mode=id_res'
- 'openid.user_setup_url=' + Server setup URL
If Server-side failed ("setup+user declined"):
idserver redirect -> UA -> consumer (cancelled):
- GET return URL (merged with the following)
- 'openid.mode=cancel'
Message validation using stateless key
consumer --> idserver:
- POST OpenID server URL (Content-Type: application/x-www-form-urlencode with properly encoded request string as body and Content-Length set)
- 'openid.mode=check_authentication'
- 'openid.assoc_handle=' + opaque handle
- 'openid.sig=' + base64(HMAC( secret(assoc_handle), token_contents ))
- 'openid.signed=mode,identity,return_to'
- 'openid.identity=' + OpenID URL
- 'openid.return_to=' + return URL
- 'openid.invalidate_handle=' + handle (optional; set it if Server marked a previous handle as expired)
idserver -> consumer: (newline-terminated key:value pairs)
- 'is_valid=' + true or false
- 'openid.invalidate_handle=' + handle (optional; set if passed handle has expired)
Server runtime errors, HTTP return codes
This section pertains to protocol/run-time errors, not authentication errors. Authentication errors are defined in the protocol.
- GET request with bad arguments but a valid return_to URL: redirect to specified URL with openid.mode=error and openid.error=Error+Text
- GET request with bad arguments: serve "400 Bad Request" with relevant body content (preferably in English)
- GET request with no arguments: serve "200" as text/html saying "This is an OpenID server endpoint. For more information, see http://openid.net/"
- POST request with bad/no arguments: serve "400 Bad request" with the key-value response format (see below), with a single key "error" with the natural language text. (and any additional keys you want)

