IndieAuth for clients: the identifiers of users, clients and servers, the discovery of a user's server, and signing in with it.
The code follows the IndieAuth Living Standard of 11 July 2024 at https://indieauth.spec.indieweb.org/ and cites its sections. The server is in dk.simongray.indieauth.server, the checks of a resource server in dk.simongray.indieauth.resource, the signed maps that carry a value through a browser in dk.simongray.indieauth.signed, and the signed state of a sign-in, which other protocols can use too, in dk.simongray.indieauth.state. The other namespaces are internal and named for what they hold, e.g. identifier for the rules of identifiers and grant for the codes and tokens of a server.
IndieAuth for clients: the identifiers of users, clients and servers, the discovery of a user's server, and signing in with it. The code follows the IndieAuth Living Standard of 11 July 2024 at https://indieauth.spec.indieweb.org/ and cites its sections. The server is in dk.simongray.indieauth.server, the checks of a resource server in dk.simongray.indieauth.resource, the signed maps that carry a value through a browser in dk.simongray.indieauth.signed, and the signed state of a sign-in, which other protocols can use too, in dk.simongray.indieauth.state. The other namespaces are internal and named for what they hold, e.g. identifier for the rules of identifiers and grant for the codes and tokens of a server.
(authorization-request
{:keys [me authorization-endpoint token-endpoint] :as discovery}
{:keys [client-id redirect-uri scope secret data] :as opts})The URL at the authorization endpoint of the discovery to send the
user's browser to, and the session to keep until the browser comes
back, as a map of :url and :session, by opts:
Keep the session for the browser's return, e.g. in a session store, or with a :secret, only its :cookie, in an HttpOnly cookie. A scope that asks for an access token needs a token endpoint, and a server without one gives a map of the :error.
The URL at the authorization endpoint of the `discovery` to send the user's browser to, and the session to keep until the browser comes back, as a map of :url and :session, by `opts`: - :client-id, the client identifier - :redirect-uri, the URL that the browser comes back to - :scope, the scopes to ask for, as a text or a collection. Without one, the answer has the user's profile URL alone. - :secret, a text of 32 bytes or more, e.g. a token, that signs the session into a cookie for state-session - :data, any EDN that the session carries for you - :now-fn and :max-cookie-bytes, as default-options has them Keep the session for the browser's return, e.g. in a session store, or with a :secret, only its :cookie, in an HttpOnly cookie. A scope that asks for an access token needs a token endpoint, and a server without one gives a map of the :error.
(callback-problem {:keys [state issuer] :as session} params)What's wrong with the query params that the browser came back with,
for the session, as a map of the :type and the :reason, or nil.
The params are a map with texts or keywords as keys. An error from the server, e.g. access_denied when the user said no, is a problem of the type ::authorization-error, with the server's :code.
What's wrong with the query `params` that the browser came back with, for the `session`, as a map of the :type and the :reason, or nil. The params are a map with texts or keywords as keys. An error from the server, e.g. access_denied when the user said no, is a problem of the type ::authorization-error, with the server's :code.
(canonical-url s)The URL that the text s names, in the canonical form that IndieAuth
compares, or nil when it names no http or https URL.
A host alone becomes an https URL, as people write it in a sign-in form. The scheme and the host are in lower case, a default port is left out, and an empty path is /. Nothing else changes, so the result can be a URL that isn't a valid profile URL.
(canonical-url "Example.com") ;; => "https://example.com/"
The URL that the text `s` names, in the canonical form that IndieAuth
compares, or nil when it names no http or https URL.
A host alone becomes an https URL, as people write it in a sign-in
form. The scheme and the host are in lower case, a default port is left
out, and an empty path is /. Nothing else changes, so the result can be
a URL that isn't a valid profile URL.
(canonical-url "Example.com") ;; => "https://example.com/"(client-id? s)Whether s is a valid client identifier: an http or https URL with a
domain name, 127.0.0.1 or [::1], without a fragment, a user name or a
password, or a . or .. segment in its path. Unlike a profile URL, it
can have a port.
Whether `s` is a valid client identifier: an http or https URL with a domain name, 127.0.0.1 or [::1], without a fragment, a user name or a password, or a . or .. segment in its path. Unlike a profile URL, it can have a port.
(client-metadata
{:keys [client-id client-name client-uri logo-uri redirect-uris] :as opts})The client metadata document to serve at the client identifier, as a
map of its JSON, by opts:
The client metadata document to serve at the client identifier, as a map of its JSON, by `opts`: - :client-id, the client identifier - :client-name, the name of the client to show the user - :client-uri, a page about the client, which is a prefix of the client identifier, and the client identifier by default - :logo-uri, the URL of its logo - :redirect-uris, the redirect URLs, which must list any on another host than the client identifier's
(client-metadata-handler opts)A Ring handler that serves the client metadata document of the opts
of client-metadata as JSON, which a page in any browser can read.
A Ring handler that serves the client metadata document of the `opts` of client-metadata as JSON, which a page in any browser can read.
(code-challenge verifier)The S256 code challenge of the code verifier: its SHA-256 in the
URL-safe letters of base64.
The S256 code challenge of the code `verifier`: its SHA-256 in the URL-safe letters of base64.
(code-verifier)A new PKCE code verifier: 43 random characters of the URL-safe letters of base64, from 32 random bytes.
A new PKCE code verifier: 43 random characters of the URL-safe letters of base64, from 32 random bytes.
(code-verifier? s)Whether s is a PKCE code verifier: 43 to 128 of the characters A-Z,
a-z, 0-9, -, ., _ and ~.
Whether `s` is a PKCE code verifier: 43 to 128 of the characters A-Z, a-z, 0-9, -, ., _ and ~.
(confirm! {:keys [urls authorization-endpoint] :as session} me opts)Confirm that the profile URL me, which the server of the session
gave, names that server, with opts, and give a map of the :me, or of
the :error. In ClojureScript, it gives a promise.
It does when me is the URL that the user gave, or one that discovery
went through, and else when the page of me names the same
authorization endpoint.
Confirm that the profile URL `me`, which the server of the `session` gave, names that server, with `opts`, and give a map of the :me, or of the :error. In ClojureScript, it gives a promise. It does when `me` is the URL that the user gave, or one that discovery went through, and else when the page of `me` names the same authorization endpoint.
The default of each option of a client that has one, which follows IndieAuth:
The default of each option of a client that has one, which follows IndieAuth: - :profile-url-pred, profile-url?, the URLs that a user can sign in as - :body-links?, false: only the head of a page names its server, since anyone who writes in its body could name another - :session-seconds, how long a user has to sign in at the server - :max-cookie-bytes, state/max-cookie-bytes: the largest cookie of a signed session, past which authorization-request throws - :max-redirects, how many redirects discovery follows - :timeout-seconds, :max-page-bytes and :max-json-bytes, the limits of each request - :send, http/send-public!, which sends to the public internet alone - :now-fn, a function that gives the instant now
(discover! url)(discover! url opts)Fetch the page of the profile URL url and the metadata of the server
that it names, with opts, and give the server as a map:
A failure gives a map of the :error, with its :type and :reason. The
opts are those of default-options and http/fetch!. In ClojureScript,
it gives a promise.
Fetch the page of the profile URL `url` and the metadata of the server that it names, with `opts`, and give the server as a map: - :me, the URL in canonical form - :urls, the URLs that discovery went through, from :me to the last redirect - :metadata-url and :metadata, unless the page has only older links - :issuer and the endpoints that the server has A failure gives a map of the :error, with its :type and :reason. The `opts` are those of default-options and http/fetch!. In ClojureScript, it gives a promise.
(endpoints response)(endpoints response opts)The URL of the metadata that the response of a profile URL names, as
a map of :metadata-url, or else the endpoints of its older links, as a
map of :authorization-endpoint and :token-endpoint, by the :body-links?
of opts. Nil when it names none.
The Link headers count before the page, and a page counts when it's HTML. A relative URL counts from the URL of the response, or from the base of the page.
The URL of the metadata that the `response` of a profile URL names, as a map of :metadata-url, or else the endpoints of its older links, as a map of :authorization-endpoint and :token-endpoint, by the :body-links? of `opts`. Nil when it names none. The Link headers count before the page, and a page counts when it's HTML. A relative URL counts from the URL of the response, or from the base of the page.
(issuer-of? issuer metadata-url)Whether the issuer identifier fits the URL of its metadata,
metadata-url: a prefix of it that ends where a path segment ends, or
the issuer whose well-known metadata it is.
Whether the `issuer` identifier fits the URL of its metadata, `metadata-url`: a prefix of it that ends where a path segment ends, or the issuer whose well-known metadata it is.
(issuer? s)Whether s is a valid issuer identifier of a server: an https URL with
a host, and without a query or a fragment.
Whether `s` is a valid issuer identifier of a server: an https URL with a host, and without a query or a fragment.
(metadata-problem {:keys [issuer authorization_endpoint token_endpoint]
:as metadata}
metadata-url)What's wrong with the metadata of a server, the map of its JSON, from
metadata-url, or nil.
What's wrong with the `metadata` of a server, the map of its JSON, from `metadata-url`, or nil.
The scopes that ask for the user's profile information rather than for an access token: profile, and email, which comes with profile alone.
The scopes that ask for the user's profile information rather than for an access token: profile, and email, which comes with profile alone.
(profile-url? s)Whether s is a valid profile URL of a user: an http or https URL with
a domain name, without a port, a fragment, a user name or a password,
or a . or .. segment in its path.
(profile-url? "https://example.com/users?id=100") ;; => true
(profile-url? "https://example.com:8443/") ;; => false
Whether `s` is a valid profile URL of a user: an http or https URL with
a domain name, without a port, a fragment, a user name or a password,
or a . or .. segment in its path.
(profile-url? "https://example.com/users?id=100") ;; => true
(profile-url? "https://example.com:8443/") ;; => false(redeem! session params)(redeem! session params opts)Finish signing in when the user's browser comes back with the query
params, for the session that authorization-request gave, with
opts, and give what the server answered: the map of its JSON, e.g.
with :me, :access_token, :scope and :profile, with the :me in canonical
form. In ClojureScript, it gives a promise.
It checks the params with callback-problem, redeems the code with
redemption-request, and confirms the :me with confirm!. A failure gives
a map of the :error, with its :type and :reason. Only the :me says who
the user is, never the :profile. The opts are those of
default-options.
Finish signing in when the user's browser comes back with the query `params`, for the `session` that authorization-request gave, with `opts`, and give what the server answered: the map of its JSON, e.g. with :me, :access_token, :scope and :profile, with the :me in canonical form. In ClojureScript, it gives a promise. It checks the params with callback-problem, redeems the code with redemption-request, and confirms the :me with confirm!. A failure gives a map of the :error, with its :type and :reason. Only the :me says who the user is, never the :profile. The `opts` are those of default-options.
(redemption response)(redemption {:keys [status body] :as response} opts)The answer in the response to a redemption-request, by the
:profile-url-pred of opts: the map of its JSON, with the :me in
canonical form, or a map of the :error.
An error of the server is of the type ::redemption-error, with the :code that the server gave, e.g. invalid_grant. An answer without a valid profile URL as its me is an error too.
The answer in the `response` to a redemption-request, by the :profile-url-pred of `opts`: the map of its JSON, with the :me in canonical form, or a map of the :error. An error of the server is of the type ::redemption-error, with the :code that the server gave, e.g. invalid_grant. An answer without a valid profile URL as its me is an error too.
(redemption-request {:keys [scope client-id redirect-uri code-verifier]
:as session}
code)The request map that redeems the code for the session: at the token
endpoint for an access token, when the session asks for one, and else
at the authorization endpoint for the profile URL alone. The body is a
hidden text of wary-fetch, since it holds the code and the verifier.
The request map that redeems the `code` for the `session`: at the token endpoint for an access token, when the session asks for one, and else at the authorization endpoint for the profile URL alone. The body is a hidden text of wary-fetch, since it holds the code and the verifier.
(refresh! session refresh-token)(refresh! session refresh-token opts)Exchange the refresh-token for a new access token at the token
endpoint of the session, with opts, and give the answer as redeem!
does. In ClojureScript, it gives a promise.
The answer has a new :refresh_token when the server gives one, which
replaces the old one, as section 5.5.1 says. The opts are those of
refresh-request and default-options.
Exchange the `refresh-token` for a new access token at the token endpoint of the `session`, with `opts`, and give the answer as redeem! does. In ClojureScript, it gives a promise. The answer has a new :refresh_token when the server gives one, which replaces the old one, as section 5.5.1 says. The `opts` are those of refresh-request and default-options.
(refresh-request session refresh-token)(refresh-request {:keys [token-endpoint client-id] :as session}
refresh-token
{:keys [scope] :as opts})The request map that exchanges the refresh-token for a new access
token at the token endpoint of the session, for the :scope of opts,
the scopes of the session by default.
A scope that the session didn't ask for throws, since a refresh asks for the same scopes or fewer.
The request map that exchanges the `refresh-token` for a new access token at the token endpoint of the `session`, for the :scope of `opts`, the scopes of the session by default. A scope that the session didn't ask for throws, since a refresh asks for the same scopes or fewer.
(revocation-request {:keys [revocation-endpoint token-endpoint] :as session}
token)The request map that revokes the token, an access token or a refresh
token, at the revocation endpoint of the session. Without one, it goes
to the token endpoint with action=revoke, as earlier versions of
IndieAuth had it.
The request map that revokes the `token`, an access token or a refresh token, at the revocation endpoint of the `session`. Without one, it goes to the token endpoint with action=revoke, as earlier versions of IndieAuth had it.
(revoke! session token)(revoke! {:keys [revocation-endpoint token-endpoint] :as session} token opts)Revoke the token, an access token or a refresh token, at the server of
the session, with opts, e.g. when the user signs out, and give a map
of the :status, or of the :error. In ClojureScript, it gives a promise.
A server revokes a refresh token with the access tokens of its grant.
The opts are those of default-options.
Revoke the `token`, an access token or a refresh token, at the server of the `session`, with `opts`, e.g. when the user signs out, and give a map of the :status, or of the :error. In ClojureScript, it gives a promise. A server revokes a refresh token with the access tokens of its grant. The `opts` are those of default-options.
(scope-text scopes)The scopes, a collection of texts, as the text of a scope parameter,
or nil for none.
The `scopes`, a collection of texts, as the text of a scope parameter, or nil for none.
(scopes x)The scopes of x, a text of scopes separated by spaces, or a collection
of them, as a vector without repeats. It's empty when there are none, and
nil when a scope has a character that a scope can't have.
The scopes of `x`, a text of scopes separated by spaces, or a collection of them, as a vector without repeats. It's empty when there are none, and nil when a scope has a character that a scope can't have.
(state-session secret state cookie)(state-session secret state cookie opts)The session that the signed cookie carries, which authorization-request
gave with the secret, if its state is the state that came back, or
nil. It's nil too when the cookie is older than the :session-seconds of
opts.
The cookie comes from the browser that came back, so that only the browser that started the sign-in can finish it.
The session that the signed `cookie` carries, which authorization-request gave with the `secret`, if its state is the `state` that came back, or nil. It's nil too when the cookie is older than the :session-seconds of `opts`. The cookie comes from the browser that came back, so that only the browser that started the sign-in can finish it.
(token)(token n)A random text of n bytes, 32 by default, that nobody can guess, e.g.
for a state, a secret or an access token. The bytes come from a secure
source, and the text is in the URL-safe letters of base64.
A random text of `n` bytes, 32 by default, that nobody can guess, e.g. for a state, a secret or an access token. The bytes come from a secure source, and the text is in the URL-safe letters of base64.
(token-scope? scopes)Whether the scopes ask for an access token: whether one of them asks
for more than profile information.
Whether the `scopes` ask for an access token: whether one of them asks for more than profile information.
(userinfo! session access-token)(userinfo! {:keys [userinfo-endpoint] :as session} access-token opts)Fetch the user's profile information from the userinfo endpoint of the
session with the access-token, which needs the profile scope, with
opts, and give the map of its JSON, or of the :error. In ClojureScript,
it gives a promise.
Like the :profile of redeem!, it never says who the user is. The opts
are those of default-options.
Fetch the user's profile information from the userinfo endpoint of the `session` with the `access-token`, which needs the profile scope, with `opts`, and give the map of its JSON, or of the :error. In ClojureScript, it gives a promise. Like the :profile of redeem!, it never says who the user is. The `opts` are those of default-options.
(well-known-url issuer)The URL of the metadata of the server whose issuer identifier is
issuer, at the well-known location of RFC 8414.
(well-known-url "https://example.com/indieauth/")
;; => "https://example.com/.well-known/oauth-authorization-server/indieauth"
The URL of the metadata of the server whose issuer identifier is
`issuer`, at the well-known location of RFC 8414.
(well-known-url "https://example.com/indieauth/")
;; => "https://example.com/.well-known/oauth-authorization-server/indieauth"cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |