\input texinfo @c -*-texinfo-*- @comment $Id@w{$} @documentlanguage en @comment %**start of header @include version.texi @settitle Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid @syncodeindex pg cp @syncodeindex fn cp @syncodeindex vr cp @syncodeindex tp cp @comment %**end of header @copying This is the manual of disfluid (version @value{VERSION}, @value{UPDATED}), an implementation of the Solid authentication protocol for guile, client and server. Copyright @copyright{} 2020, 2021 Vivien Kraus @quotation Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License'' @end quotation @end copying @dircategory Software libraries @direntry * disfluid: (disfluid)Interoperability on the web @end direntry @titlepage @title Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid @subtitle for version @value{VERSION}, @value{UPDATED} @author Vivien Kraus (@email{vivien@@planete-kraus.eu}) @page @vskip 0pt plus 1fill @insertcopying @end titlepage @contents @ifnottex @node Top @top Disfluid @end ifnottex Disfluid is an independent implementation of a web stack focusing on interoperability. In this implementation, the users control what programs run in their computers. They also choose who to trust for online data storage and processing, without needing any permission, or can self-host their data. The software is available at @url{https://labo.planete-kraus.eu/webid-oidc.git}. The latest commit is tracked in the Guix channel @url{https://labo.planete-kraus.eu/webid-oidc-channel.git}. @ifnottex A PDF version of this manual is available at @url{https://disfluid.planete-kraus.eu/disfluid.pdf}. @end ifnottex @menu * Decentralized Authentication on the Web:: * Invoking disfluid:: * Running disfluid with GNU Guix:: * Common parameters:: * Managing keys:: * OIDC discovery:: * Client manifest:: * The Json Web Token:: * Caching on server side:: * The HTTP Link header:: * Content negociation:: * Server endpoints:: * Resources stored on the server:: * Running a client:: * Serialization to (S)XML:: * Exceptional conditions:: * GNU Free Documentation License:: * Index:: @end menu @node Decentralized Authentication on the Web @chapter Decentralized Authentication on the Web Authentication on the web is currently handled in the following way: anyone can install a server that will authenticate users on the web. The problem is interoperability. If a client (an application) wants to authenticate a user, it has to be approved by the authentication server. In other words, if @var{useful-program} wants to authenticate @var{MegaCorp} users, then @var{useful-program} has to register to @var{MegaCorp} first, and get approved. This goes against the principle of permission-less innovation, which is at the heart of the web. In the decentralized authentication web, the best attempt so far is that of ActivityPub. All servers are interoperable with respect to authentication: if user A emits an activity, it is forwarded by A's server to its recipients, and A's server is responsible for A's identity. The problem with that approach is that the data is tied to the application. It is not possible to use another application to process the data differently, or to use multiple data sources, in an interoperable way (without the ActivityPub server knowing). This means that on Activitypub, microblogging applications will not present different activities correctly. This also means that it is difficult to write a free replacement to a non-free application program, because it would need to manage the data. In the Solid ecosystem, there is a clear distinction between servers and applications. An application is free to read data from all places at the same time, using a permission-less authentication system. Since the applications do not need to store data, the cost of having users is neglectible, so users do not need prior approval before using them (making captchas and the like a thing of the past). Servers do not have a say in which applications the user uses. The authentication used is a slight modification of the well-established OpenID Connect. It is intended to work in a web browser, but this package demonstrates that it also works without a web browser. @node Invoking disfluid @chapter Invoking disfluid The @samp{disfluid} program runs a server, if the user specifies a configuration file, or the graphical browser otherwise. @menu * General options:: * Running a server:: @end menu @node General options @section General options The server will respond to @samp{-h} and @samp{-v} commands, to get the help output and the version information. The server output (command-line, logs) are localized for the system administrator. You can control it with the @samp{LANG} environment variable. So if your locale is not English, you can have the same commands as in this manual by running with @code{LANG=C}. The programs respect the @samp{XDG_DATA_HOME} (if not overriden by the server configuration) and @samp{XDG_CACHE_HOME} to store persistent data and disposable data. The cache directory can be deleted at any time. If one of these variables is not set, its value is computed from the @samp{HOME} environment variable. @node Running a server @section Running a server The disfluid code is published under the Affero GPL, which means that the service provider needs to publish all changes made to the program to users over the network. The @samp{disfluid} command provides a @samp{--complete-corresponding-source} option so that the system administrator can specify a means to download the source. The servers will add a @samp{Source:} header in each response, containing the value of this configuration option. It can be, for instance, an URI where to download the modified source code. The servers can be configured to redirect output and errors to a log file and an error file, with the @samp{--log-file} and @samp{--error-file} options. The server will listen to port 8080 by default, but this may be configured with @samp{--port}. Since the servers do not support TLS, and they only support HTTP/1.1, they are intended to run behind a reverse proxy (even for the authenticating reverse proxy). Finally, you configure the server by passing the @samp{--configuration} parameter pointing to a configuration file. The configuration file is plain guile code, that must evaluate to an @code{}. Here is an example configuration that runs a resource server with an identity provider: @lisp (use-modules (webid-oidc server endpoint) (webid-oidc server endpoint resource-server) (webid-oidc server endpoint identity-provider) (webid-oidc server endpoint authentication) (webid-oidc oidc-configuration) (oop goops)) (make #:host "example.com" #:oidc-discovery (make #:path "/.well-known/openid-configuration" #:configuration (make #:jwks-uri "https://example.com/keys" #:authorization-endpoint "https://example.com/authorize" #:token-endpoint "https://example.com/token")) #:authorization-endpoint (make #:path "/authorize" #:subject "https://example.com/profile/card#me" #:encrypted-password (crypt "secretpassword123" "$6$secret.salt") #:key-file "/var/lib/disfluid/key-file.jwk") #:token-endpoint (make #:path "/token" #:issuer "https://example.com" #:key-file "/var/lib/disfluid/key-file.jwk") #:jwks-endpoint (make #:path "/keys" #:key-file "/var/lib/disfluid/key-file.jwk") #:default (make #:backend (make #:server-name "https://example.com" #:owner "https://example.com/profile/card#me") #:server-uri "https://example.com")) @end lisp The server will make requests on the world-wide web, for instance to download client manifests. The requests can be redirected with XML Catalog, by setting the @samp{XML_CATALOG_FILES} to a space-separated list of URIs (can be @code{file:} URIs). The requests cannot be directed to the file system. @node Running disfluid with GNU Guix @chapter Running disfluid with GNU Guix The channel at @url{https://labo.planete-kraus.eu/webid-oidc-channel.git} can be used with guix. It defines the package at the latest commit, and a service definition in @emph{(vkraus services disfluid)}. @defvr {service type} disfluid-service-type This service runs a disfluid server with the @emph{disfluid} system user. The value it takes is a service configuration. @end defvr @deftp {configuration record} [@var{disfluid}] @var{complete-corresponding-source} @var{port} @var{configuration-file} [@var{extra-options}] The configuration for the identity provider. The optional @var{disfluid} argument is the package containing the binary to run, if you want to apply some patches, and @var{extra-options} is an empty list by default. @var{configuration-file} is a file-like object or a file name. @end deftp @node Common parameters @chapter Common parameters The @emph{(webid-oidc parameters)} module provides a set of Guile parameter to control the program behavior. @deffn {parameter} data-home This parameter controls the location where the program stores persistent data. By default, it is located in @code{XDG_DATA_HOME}. @end deffn @deffn {parameter} cache-home This parameter controls the location where the program stores data that might get deleted at any time. By default, it uses @code{XDG_CACHE_HOME}. @end deffn @deffn {parameter} current-date This parameter is a thunk similar to SRFI-19 @code{current-date}, except it can be set with a thunk returning a date, time or number of seconds, or a date, time or number of seconds. @end deffn @deffn {parameter} anonymous-http-request This parameter is a function similar to the @code{http-request} function in @emph{(web client)}. @end deffn @deffn {parameter} authorization-code-default-validity This parameter controls the number of seconds for which an authorization code is valid at creation time. @end deffn @deffn {parameter} oidc-token-default-validity This parameter controls the number of seconds for which an ID token or access token is valid at creation time. @end deffn @deffn {parameter} dpop-proof-validity This parameter controls the number of seconds for which a DPoP proof is valid after it has been issued. @end deffn @node Managing keys @chapter Managing keys Some functions require a key, or a key pair, to operate. The @emph{(webid-oidc jwk)} module provides you with everything required to manage keys. @deftp {Class} () @var{alg} This is the base class for a private key. You need it to issue signatures. Signatures issued with this key will use @var{alg} for the signature algorithm, but the public key associated with this private key will verify signatures in any compatible algorithm, not just @var{alg}. @var{alg} is a symbol, for instance @code{'RS256}. @end deftp @deftp {Class} () This is the base class for a public key. You need it to check signatures. @end deftp @deftp {Class} () @var{public-key} @var{private-key} A key pair contains a @var{public-key} and a matching @var{private-key}. You use this form for keys you own. @end deftp @deftp {Class} () (@code{}) This key pair contains matching RSA keys. @end deftp @deftp {Class} () (@code{}) @var{crv} This key pair contains matching elliptic curve keys. @var{crv} is a symbol identifiying the curve. @end deftp @deftp {Class} () @var{d} @var{p} @var{q} @var{dp} @var{dq} @var{qi} @deftpx {Class} () @var{n} @var{e} @deftpx {Class} () @var{crv} @var{z} @deftpx {Class} () @var{crv} @var{x} @var{y} All fields are strings, base64 encoding the parameters, except @var{crv}, which is a symbol. @end deftp @deftp {Class} () @var{keys} An identity provider may use different keys that are in validity to sign different access tokens. The JWKS encapsulates many public @var{keys}. @end deftp @deftypefn {Generic method} public-key (@var{key} @code{}) @deftypefnx {Generic method} public-key (@var{key} @code{}) Return the public part of @var{key}, which may either be a key pair or a public key. @end deftypefn @deftypefn {Generic method} private-key (@var{key} @code{}) @deftypefnx {Generic method} private-key (@var{key} @code{}) Return the private part of @var{key}. @end deftypefn @deftypefn {Generic method} rsa-d (@var{key} @code{}) @deftypefnx {Generic method} rsa-d (@var{key} @code{}) @deftypefnx {Generic method} rsa-p (@var{key} @code{}) @deftypefnx {Generic method} rsa-p (@var{key} @code{}) @deftypefnx {Generic method} rsa-q (@var{key} @code{}) @deftypefnx {Generic method} rsa-q (@var{key} @code{}) @deftypefnx {Generic method} rsa-dp (@var{key} @code{}) @deftypefnx {Generic method} rsa-dp (@var{key} @code{}) @deftypefnx {Generic method} rsa-dq (@var{key} @code{}) @deftypefnx {Generic method} rsa-dq (@var{key} @code{}) @deftypefnx {Generic method} rsa-qi (@var{key} @code{}) @deftypefnx {Generic method} rsa-qi (@var{key} @code{}) @deftypefnx {Generic method} rsa-n (@var{key} @code{}) @deftypefnx {Generic method} rsa-n (@var{key} @code{}) @deftypefnx {Generic method} rsa-e (@var{key} @code{}) @deftypefnx {Generic method} rsa-e (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-crv (@var{key} @code{}) @deftypefnx {Generic method} ec-x (@var{key} @code{}) @deftypefnx {Generic method} ec-x (@var{key} @code{}) @deftypefnx {Generic method} ec-y (@var{key} @code{}) @deftypefnx {Generic method} ec-y (@var{key} @code{}) @deftypefnx {Generic method} ec-z (@var{key} @code{}) @deftypefnx {Generic method} ec-z (@var{key} @code{}) @deftypefnx {Generic method} alg (@var{key} @code{}) @deftypefnx {Generic method} alg (@var{key} @code{}) Key parameter getters. @end deftypefn @deftypefn {Generic method} keys (@var{jwks} @code{}) Return all the public keys used by @var{jwks}. @end deftypefn @deftypefn {Generic method} check-key (@var{key} @code{}) @deftypefnx {Generic method} check-key (@var{key} @code{}) @deftypefnx {Generic method} check-key (@var{key} @code{}) Check that the @var{key} parameters are consistent. @end deftypefn When exchanging keys, maybe you will have them in the form of a JWK: an alist from symbols to strings, as a representation for a JSON object. @deftypefn {Generic method} key->jwk (@var{key} @code{}) @deftypefnx {Generic method} key->jwk (@var{key} @code{}) @deftypefnx {Generic method} key->jwk (@var{key} @code{}) Return an alist with known parameter names for JSON. @end deftypefn @deffn function jwk->key @var{jwk} Parse @var{jwk} as a key or a key pair. @end deffn @deftypefn {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) @deftypefnx {Generic method} kty (@var{key} @code{}) Return @code{'RSA} for RSA keys, or @code{'EC} for elliptic curve keys. @end deftypefn @deftypefn {Generic method} jkt (@var{key} @code{}) @deftypefnx {Generic method} jkt (@var{key} @code{}) Hash the @var{key} parameters in a reproducible order to get the hash of a key. @end deftypefn @deffn function generate-key @var{[#:n-size]} @var{[#:e-size]} @var{[#:e=\"AQAB\"]} @var{[#:crv]} Generate a new key pair. @end deffn @deftypefn {Generic method} serve (@var{jwks} @code{}) @var{expiration-date} Return a response and response body for serving @var{jwks}. Client-side caching is very much necessary for a JWKS, so pass @var{expiration-date} as a SRFI-19 date to define a maximum date for caching. It should be in the future, for instance in 1 hour. @end deftypefn @deffn {function} get-jwks @var{uri} [#:@var{http-request}] Download a JWKS on the web at @var{uri}. Use @var{http-request}, with the same interface as that of @emph{(web client)}, to actually get the JWKS. @end deffn @deftp {Exception type} ¬-a-jwk If the key parameters are incorrect, this exception is raised. @end deftp @deftp {Exception type} ¬-a-jwks If the JWKS cannot be downloaded, or is incorrect, this exception is raised. @end deftp @node OIDC discovery @chapter OIDC discovery An identity provider is known by its server name. The different endpoints can be discovered from there. @deftp {Class} () @var{jwks-uri} @var{authorization-endpoint} @var{token-endpoint} The OIDC configuration for an identity provider. @var{jwks-uri}, @var{authorization-endpoint} adn @var{token-endpoint} are all URIs. You can construct an OIDC configuration two different ways: @itemize @item by passing @code{#:@var{jwks-uri}}, @code{#:@var{authorization-endpoint}} and @code{#:@var{token-endpoint}} to the constructor; @item by passing @code{#:@var{server}}, and optionally @code{#:@var{http-request}} to the constructor, to query the @var{server} for its configuration. @end itemize @end deftp @deftp {Exception type} &invalid-oidc-configuration This exception is raised when the configuration is unusable or incomplete. @end deftp @deffn {function} make-invalid-oidc-configuration Constructor for the @code{&invalid-oidc-configuration} exception type. @end deffn @deffn {function} invalid-oidc-configuration? @var{exception} Check whether @var{exception} was raised because of an invalid OIDC configuration. @end deffn @deffn {Generic} jwks-uri @var{oidc-configuration} Return the JWKS uri of @var{oidc-configuration}. @end deffn @deffn {Generic} jwks @var{oidc-configuration} Query the JWKS uri of @var{oidc-configuration}. @end deffn @deffn {Generic} authorization-endpoint @var{oidc-configuration} Return the authorization endpoint of @var{oidc-configuration}. @end deffn @deffn {Generic} token-endpoint @var{oidc-configuration} Return the token endpoint of @var{oidc-configuration}. @end deffn @deffn {Generic} serve @var{configuration} @var{expiration-date} Return 2 values: the response, and response body, needed to serve @var{configuration}. It is very much recommended to let clients cache this value. They will not revalidate it until after @var{expiration-date}, a SRFI-19 date. @end deffn @node Client manifest @chapter Client manifest To make sure that a client application is legitimate, it is mandated that it serves a public document under its ID URI, and that document should confirm the URI and the redirection URI, where the client application gets the authorization code. @deftp {Class} () @var{client-id} @var{redirect-uris} This is the class encapsulating a very basic client manifest. @var{client-id} is an URI, and @var{redirect-uris} is a list of URIs. You can construct one by providing both @code{#:@var{client-id}} and @code{#:@var{redirect-uris}}, or by providing only @code{#:@var{client-id}}, in which case it will be downloaded from the web. @end deftp Clients that cannot serve pages should use the anonymous client ID, that accepts all redirect URIs. @deffn {Generic} client-id @var{manifest} Return the client ID of @var{manifest}. @end deffn @deffn {Generic} redirect-uris @var{manifest} Return the list of accepted redirection URIs for @var{manifest}. @end deffn @deffn {Generic} ->json-data @var{manifest} Convert @var{manifest} to JSON data (alists for objects, vectors for arrays). You should override this method if you design an extended client manifest class. @end deffn @deffn {Generic} check-redirect-uri @var{manifest} @var{uri} Check that @var{manifest} controls @var{uri}, where to send the authorization code. Raises an exception if that’s not the case. @end deffn @deftp {Exception type} &invalid-client-manifest This exception is raised when the client manifest is invalid. @end deftp @deffn {function} make-invalid-client-manifest Constructor for the @code{&invalid-client-manifest} exception type. @end deffn @deffn {function} invalid-client-manifest? @var{exception} Check whether @var{exception} was raised because of an invalid client manifest. @end deffn @deftp {Exception type} &unauthorized-redirect-uri This exception is raised when the requested authorization URI is unauthorized. @end deftp @deffn {function} make-unauthorized-redirect-uri Constructor for the @code{&unauthorized-redirect-uri} exception type. @end deffn @deffn {function} unauthorized-redirect-uri? @var{exception} Check whether @var{exception} was raised because of an unauthorized redirection URI. @end deffn @deftp {Exception type} &inconsistent-client-manifest This exception is raised when the client ID does not match what the client manifest says. @end deftp @deffn {function} make-inconsistent-client-manifest Constructor for the @code{&inconsistent-client-manifest} exception type. @end deffn @deffn {function} inconsistent-client-manifest? @var{exception} Check whether @var{exception} was raised because of an inconsistent client manifest. @end deffn @deftp {Exception type} &cannot-serve-public-manifest This exception is raised when the manifest to serve has the public client URI as ID. @end deftp @deffn {function} make-cannot-serve-public-manifest Constructor for the @code{&cannot-serve-public-manifest} exception type. @end deffn @deffn {function} cannot-serve-public-manifest? @var{exception} Check whether @var{exception} was raised because the server wants to serve a public manifest. @end deffn @deftp {Exception type} &cannot-fetch-client-manifest This exception is raised when the server does not behave correctly when fetching the manifest. @end deftp @deffn {function} make-cannot-fetch-client-manifest Constructor for the @code{&cannot-fetch-client-manifest} exception type. @end deffn @deffn {function} cannot-fetch-client-manifest? @var{exception} Check whether @var{exception} was raised because we could not fetch a client manifest. @end deffn @node The Json Web Token @chapter The Json Web Token The Json Web Token, or @dfn{JWT}, is a terse representation of a pair of JSON objects: the @dfn{header}, and the @dfn{payload}. The JWT can be @dfn{encoded} as a Json Web Signature (@dfn{JWS}), in which case the header is encoded to base64 with the URL alphabet, and without padding characters, the payload is also encoded to base64, and the concatenation of the encoding of the header, a dot, and the encoding of the payload is signed with some cryptography algorithm. In the following, we will only be interested by public-key cryptography. The concatenation of header, dot, payload, dot and signature in base64 is the encoding of the JWT. @menu * Tokens:: * Tokens issued by an OIDC provider:: * Date verification for tokens:: * Single-use tokens:: * ID tokens:: * Access tokens:: * DPoP proofs:: * Authorization codes:: @end menu @node Tokens @section Tokens The @emph{(webid-oidc jws)} implements some functionality for tokens. @deftp {Class} () @var{alg} The base class for all tokens. It only knows the signature @var{alg}orithm. You can construct one in different ways: @itemize @item the @code{#:@var{alg}} construct keyword supports a string or a keyword as a value, containing a valid JWA identifier, such as @code{RS256}; @item the @code{#:@var{signing-key}} keyword defines the key that will serve to sign the token. The signature algorithm is set to the default of @var{signing-key}; @item the @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}} keywords let you pass two alists, following the JSON representation from srfi-180: objects are alists of @strong{symbols} to values, arrays are vectors. @end itemize @end deftp @deftp {Exception type} &invalid-jws This exception is raised when a JWT cannot be parsed or constructed as a JWS. @end deftp @deffn {function} make-invalid-jws Construct an exception of type @code{&invalid-jws}. @end deffn @deffn {function} invalid-jws? @var{exception} Check whether @var{exception} was raised because of an invalid JWS. @end deffn There are multiple things you can do with a token. @deffn {Generic} alg @var{token} Return the signature algorithm used for @var{token}, as a symbol. @end deffn @deffn {Generic} token->jwt @var{token} Return two alists, following the JSON representation from srfi-180: one for the header, and then one for the payload. @end deffn @deffn {Generic} lookup-keys @var{token} @var{args} Return the set of keys that could be used to sign @var{token}, as a public key, a list of keys, or a JWKS. @var{args} is a list of keyword arguments for specific implementations. @end deffn @deffn {Generic} verify @var{token} @var{args} Suppose that the @var{token} signature has been checked, perform some additional verifications. This function should raise exceptions to signal an invalid token. @end deffn @deffn {function} decode @var{expected-token-class} @var{encoded} . @var{args} Parse @var{encoded} as a token from the @var{expected-token-class}, check its signature against the key obtained by @code{(lookup-keys @var{token} @var{args})} where @var{token} is the parsed token, and perform additional verifications with @code{(verify @var{token} @var{args})}. @end deffn @deffn {function} encode @var{token} @var{key} Encode and sign @var{token} with @var{key}, returning a string. @end deffn @deffn {function} issue @var{token-class} @var{issuer-key} . @var{args} Construct a token of @var{token-class} and @var{args} and sign it with @var{issuer-key}. Since we know the key to sign it, it is not necessary to pass either @code{#:signing-key} nor @code{#:alg} to the constructor. @end deffn @node Tokens issued by an OIDC provider @section Tokens issued by an OIDC provider OIDC tokens are those signed by an OIDC identity provider. This kind of token knows its issuer, and getting the keys to check the token signature is done by OIDC discovery. @deftp {Class} () @var{iss} The base class for tokens which are issued by an identity provider. It knows the issuer (@var{iss}, an uri from @emph{(web uri)}), and can query it to check the token signature. Similarly to the base token type, you can construct one by specifying its arguments, or create one from a pair of alists. @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}} is required to construct the base token; @item @code{#:@var{iss}} specifies the issuer. @end itemize The main point of this class is to provide a method for the @code{lookup-keys} generic. This method accepts one keyword argument, @code{#:@var{http-request}}, a function that behaves like the web client in @emph{(web client)}. You can set this value as a keyword argument in the @code{decode} function. @end deftp @deffn {Generic} iss @var{token} Return the issuer of @var{token}, as an URI. @end deffn @deftp {Exception type} &cannot-query-identity-provider @var{identity-provider} This exception is raised when the OIDC discovery fails. @var{identity-provider} is an URI. @end deftp @deffn {function} make-cannot-query-identity-provider @var{identity-provider} Construct an exception of type @code{&cannot-query-identity-provider}. @end deffn @deffn {function} cannot-query-identity-provider? @var{exception} Check whether @var{exception} was raised because an identity provider could not be queried. @end deffn @deffn {function} cannot-query-identity-provider-value @var{exception} Return the faulty identity provider for @var{exception}. @end deffn @node Date verification for tokens @section Date verification for tokens Different kinds of tokens have a requirement for a limited time window for which the signature should be valid. @deftp {Class} () @var{iat} @var{exp} The base class for tokens which are issued for a limited time window. It knows the issuance date (@var{iat}, a date from @emph{(srfi srfi-19)}), and the expiration date (@var{iat}, a date from @emph{(srfi srfi-19)}). Similarly to the base token type, you can construct one by specifying its arguments, or create one from a pair of alists. @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}} is required to construct the base token; @item @code{#:@var{iat}} specifies the issuance date. It defaults to the current date; @item @code{#:@var{exp}} specifies the expiration date. If it is not set, the value will be computed from @var{iat} and @var{validity}; @item @code{#:@var{validity}} is used when the expiration date is not known in advance. It is a number of seconds. For a DPoP proof, the value should be around 30 seconds. For an access token, a good value is in the ballpark of 3600 seconds (an hour). Defaults to 3600 seconds, but be aware that for single-use tokens, this value will be ignored and replaced with a much shorter time. @end itemize The main point of this class is to provide a stricter token validation function. You can customize the current date by passing @code{#:@var{current-date} ...} as keyword arguments to @code{decode}. @code{...} would be replaced with a time or date. @end deftp @deffn {Generic} default-validity @var{token} Return the default validity as a number of seconds to construct @var{token}, or @code{#f} if an explicit @code{#:validity} is required. @end deffn @deffn {Generic} has-explicit-exp? @var{token} Check whether we should trust the JWT exp field when constructing @var{token}. DPoP proofs should not be able to fill our cache with infinitely-valid proofs, so it is disabled for DPoP proofs. @end deffn @deffn {Generic} iat @var{token} Return the signature date of @var{token}, as a srfi-19 date. @end deffn @deffn {Generic} exp @var{token} Return the expiration date of @var{token}, as a srfi-19 date. @end deffn @deftp {Exception type} &signed-in-future @var{signature-date} @var{current-date} @deftpx {Exception type} &expired @var{expiration-date} @var{current-date} An exception of type @code{&signed-in-future} is raised when the current date is before the alleged signature date. Since the signing entity and the verifier entity may not be on the same system, the clocks may be slightly out of synchronization, so a margin of 5 seconds is usually accepted. An exception of type @code{&expired} indicates that the signature is no longer valid. @end deftp @deffn {function} make-signed-in-future @var{signature-date} @var{current-date} @deffnx {function} make-expired @var{expiration-date} @var{current-date} Constructors for the @code{&signed-in-future} and @code{&expired} exception types. @end deffn @deffn {function} signed-in-future? @var{exception} @deffnx {function} expired? @var{exception} Check whether @var{exception} was raised because of a date mismatch. @end deffn @deffn {function} error-signature-date @var{exception} @deffnx {function} error-expiration-date @var{exception} @deffnx {function} error-current-date @var{exception} If @var{exception} was raised because of a date mismatch, return the signature, expiration or current date. @end deffn @node Single-use tokens @section Single-use tokens To prevent replay attacks, you might want to assign an unique identifier to each token of some kind. If you have an expiration date, you could remember that this identifier has been seen, and forget about it as soon as the token expires. For this to work, you would need an expiration date for your single-use token: this is why we only support it for time-bound tokens, and the validity is reduced down to 2 minutes. @deftp {Class} () @var{nonce} The base class for tokens which are intended to be decoded only once. The unique identifier string @var{nonce} will be remembered as long as the program is running and the token is not expired. Similarly to the base token type, you can construct one by specifying its arguments, or create one from a pair of alists. @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}} is required to construct the base token; @item @code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}} is required to construct the time-bound token; @item @code{#:@var{nonce}} specifies the unique identifier. It defaults to a random string of base64 data encoding 96 bits of entropy. @item @end itemize The main point of this class is to provide an even stricter token validation function, that can only be run once for a given token (with reasonable limits: if the program is killed, it won’t remember the tokens from before). You can customize the current date by passing @code{#:@var{current-date} ...} as keyword arguments to @code{decode}, just as you do for regular time-bound tokens. @code{...} would be replaced with a time or date. @end deftp @deffn {Generic} nonce-field-name @var{token} When constructing @var{token} from an existing JWT, this method gives the field name in the JWT payload that represents the nonce. DPoP proofs use @code{'jti}, so they override this value. @end deffn @deffn {Generic} nonce @var{token} Return the unique identifier of @var{token}, as a string. @end deffn @deftp {Exception type} &nonce-found @var{nonce} If a token with the same nonce has already been decoded during its life time, this exception is raised with the duplicated @var{nonce}. @end deftp @deffn {function} make-nonce-found @var{nonce} Construct an exception of type @code{&nonce-found}. @end deffn @deffn {function} nonce-found? @var{exception} Check whether @var{exception} was raised because a single-use token was already parsed. @end deffn @deffn {function} nonce-found-nonce @var{exception} Return the faulty nonce in @var{exception}. @end deffn @node ID tokens @section ID tokens The @emph{(webid-oidc oidc-id-token)} module contains a definition for the OIDC ID token. @deftp {Class} ( ) @var{webid} @var{sub} @var{aud} The ID token is issued by an identity provider, and is intended to be used by the client only. It gives information about the user identified by a @var{webid}, an URI from @emph{(web uri)}, and the client ID, @var{aud}, an URI too. Since the client should not communicate this token, it is reasonable to think that the client will deccode the token as soon as it gets it, and then forget the now useless signature. This is why this token is considered single-use. The @var{sub} field should store a username as a string, but if it is missing, the webid (as a string) will be used. To construct an ID token, you would either need @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any token, or a combination of parameters: @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT; @item @code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}}, because it is issued for a limited time window (around an hour); @item @code{#:@var{nonce}} to define its identifier (defaults to a random one); @item @code{#:@var{iss}}, the issuer URI, because it is an OIDC token; @item @code{#:@var{webid}}, an URI identifying the user; @item @code{#:@var{sub}}, a string that defaults to the webid; @item @code{#:@var{aud}}, an URI identifying the application. @end itemize @end deftp @deffn {Generic} webid @var{token} Return the user identifier in @var{token}, as an URI. @end deffn @deffn {Generic} sub @var{token} Return the username in @var{token}, as a string. @end deffn @deffn {Generic} aud @var{token} Return the client identifier in @var{token}, as an URI. @end deffn @deftp {Exception type} &invalid-id-token This exception is raised when the ID token is invalid. @end deftp @deffn {function} make-invalid-id-token Construct an exception of type @code{&invalid-id-token}. @end deffn @deffn {function} invalid-id-token? @var{exception} Check whether @var{exception} was raised because of an invalid ID token. @end deffn @node Access tokens @section Access tokens The @emph{(webid-oidc access-token)} module contains a definition for the OIDC access token. @deftp {Class} ( ) @var{webid} @var{aud} @var{client-id} @var{cnf/jkt} The access token is issued by an identity provider for a client, and is intended to be used by the resource servers. It indicates that the agent possessing a key hashed to @var{cnf/jkt} (a string) is identified by @var{client-id} (an URI) and is authorized to act on behalf of the user identified by @var{webid} (an URI). For compatibility, @var{aud} should be set to the literal string @code{"solid"}. The agent demonstrates that it owns this key by issuing a DPoP proof alongside the access token. To construct an access token, you would either need @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any token, or a combination of parameters: @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT; @item @code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}}, because it is issued for a limited time window (around an hour); @item @code{#:@var{iss}}, the issuer URI, because it is an OIDC token; @item @code{#:@var{webid}}, an URI identifying the user; @item @code{#:@var{client-id}}, an URI identifying the client; @item @code{#:@var{cnf/jkt}}, the hash of a public key whose private key is owned by the client, or @code{#:@var{client-key}}, the client key itself; @item @code{#:@var{aud}}, literally @code{"solid"}, optional, defaults to the correct value. @end itemize Since the same access token is presented on each request, it is not single-use. @end deftp @deffn {Generic} webid @var{token} Return the user identifier in @var{token}, as an URI. @end deffn @deffn {Generic} client-id @var{token} Return the client identifier in @var{token}, as an URI. @end deffn @deffn {Generic} cnf/jkt @var{token} Return the hash of the client key, as a string. @end deffn @deffn {Generic} aud @var{token} Return @code{"solid"}. @end deffn @deftp {Exception type} &invalid-access-token This exception is raised when the access token is invalid. @end deftp @deffn {function} make-invalid-access-token Construct an exception of type @code{&invalid-access-token}. @end deffn @deffn {function} invalid-access-token? @var{exception} Check whether @var{exception} was raised because of an invalid access token. @end deffn @node DPoP proofs @section DPoP proofs The @emph{(webid-oidc dpop-proof)} module contains a definition for the DPoP proof token. @deftp {Class} () @var{typ} @var{jwk} @var{htm} @var{htu} @var{ath} The DPoP proof is a token that is issued by the client, and presented to the resource server along with an access token. It is only valid for one request, and for one use. So, it should have a very short validity frame, for instance 30 seconds, and should only be valid for a specific request method @var{htm} and a specific request URI @var{htu}, down to the path, but ignoring the query and fragment. The DPoP proof is the proof of possession of @var{jwk}, a public key. It should always have a @var{typ} field set to @code{"dpop+jwt"}. To construct a DPoP proof, you would either need @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any token, or a combination of parameters: @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT; @item @code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}}, because it is issued for a limited time window (around 30 seconds); @item @code{#:@var{nonce}}, because it is single-use; @item @code{#:@var{jwk}}, the public key whose possession we demonstrate by signing the proof; @item @code{#:@var{htm}}, the HTTP method used (as a symbol); @item @code{#:@var{htu}}, the HTTP URI used (as an URI); @item @code{#:@var{ath}}, the hash of the access token that goes with this proof, or @code{#:@var{access-token}}, the encoded access token itself, if the proof goes with an access token. Otherwise, pass @code{#f}. Defaults to @code{#f}; @item @code{#:@var{typ}}, literally @code{"dpop+jwt"}, optional, defaults to the correct value. @end itemize This token class makes a stricter verification function. It requires you to set as a keyword argument in @code{decode} the following parameters: @table @code @item #:@var{access-token} set the access token that should go with the proof, defaults to @code{#f} (no access token); @item #:@var{method} set the method used for the proof; @item #:@var{uri} set the URI used for the proof; @item #:@var{cnf/check} set the expected hash of the key used by the DPoP proof, or a function taking a public key hash. If this is a function, it should raise an exception if the hash is invalid, because its return value is ignored. @end table @end deftp @deffn {Generic} jwk @var{proof} Return the public key whose possession @var{proof} demonstrates. @end deffn @deffn {Generic} htm @var{proof} Return the HTTP method in @var{proof}, as a symbol. @end deffn @deffn {Generic} htu @var{proof} Return the HTTP URI in @var{proof}, as an URI. @end deffn @deffn {Generic} ath @var{proof} Return the hash of the access token that should go with @var{proof}, or @code{#f} if @var{proof} is not used with an access token. @end deffn @deffn {Generic} typ @var{proof} Return @code{"dpop+jwt"}. @end deffn @deftp {Exception type} &invalid-dpop-proof This exception is raised when the DPoP proof is invalid. @end deftp @deffn {function} make-invalid-dpop-proof Construct an exception of type @code{&invalid-dpop-proof}. @end deffn @deffn {function} invalid-dpop-proof? @var{exception} Check whether @var{exception} was raised because of an invalid DPoP proof. @end deffn @deftp {Exception type} &dpop-method-mismatch @var{advertised} @var{actual} This exception is raised when the @var{advertised} method is not what is @var{actual}ly used in the request (both symbols). @end deftp @deffn {function} make-dpop-method-mismatch @var{advertised} @var{actual} Construct an exception of type @code{&dpop-method-mismatch}. @end deffn @deffn {function} dpop-method-mismatch? @var{exception} Check whether @var{exception} was raised because of a difference between the advertised and actual HTTP methods used. @end deffn @deffn {function} dpop-method-mismatch-advertised @var{exception} In case of a DPoP method mismatch causing @var{exception}, return the method used in the proof signature. @end deffn @deffn {function} dpop-method-mismatch-actual @var{exception} In case of a DPoP method mismatch causing @var{exception}, return the method that the server received. @end deffn @deftp {Exception type} &dpop-uri-mismatch @var{advertised} @var{actual} This exception is raised when the @var{advertised} URI is not what is @var{actual}ly used in the request (both URIs). @end deftp @deffn {function} make-dpop-uri-mismatch @var{advertised} @var{actual} Construct an exception of type @code{&dpop-uri-mismatch}. @end deffn @deffn {function} dpop-uri-mismatch? @var{exception} Check whether @var{exception} was raised because of a difference between the advertised and actual HTTP URIs used. @end deffn @deffn {function} dpop-uri-mismatch-advertised @var{exception} In case of a DPoP URI mismatch causing @var{exception}, return the URI used in the proof signature. @end deffn @deffn {function} dpop-uri-mismatch-actual @var{exception} In case of a DPoP URI mismatch causing @var{exception}, return the URI that the server received. @end deffn @deftp {Exception type} &dpop-invalid-ath @var{hash} @var{access-token} This exception is raised when the DPoP proof is intended for use along with an access token identified by @var{hash}, but is actually used along with @var{access-token}. @end deftp @deffn {function} make-dpop-invalid-ath @var{hash} @var{access-token} Construct an exception of type @code{&dpop-invalid-ath}. @end deffn @deffn {function} dpop-invalid-ath? @var{exception} Check whether @var{exception} was raised because the DPoP proof was not used with the correct access token. @end deffn @deffn {function} dpop-invalid-ath-hash @var{exception} In case of a DPoP presented with the wrong access token, causing @var{exception}, return the hash of the intended access token. @end deffn @deffn {function} dpop-invalid-ath-access-token @var{exception} In case of a DPoP presented with the wrong access token, causing @var{exception}, return the actual access token. @end deffn @deftp {Exception type} &dpop-unconfirmed-key This exception is raised when the DPoP proof does not demonstrate the possession of the correct key. @end deftp @deffn {function} make-dpop-unconfirmed-key Construct an exception of type @code{&dpop-unconfirmed-key}. @end deffn @deffn {function} dpop-unconfirmed-key? @var{exception} Check whether @var{exception} was raised because the DPoP proof demonstrated the possession of an incorrect key. @end deffn @node Authorization codes @section Authorization codes @emph{(webid-oidc authorization-code)} defines an authorization code type. @deftp {Class} () @var{webid} @var{client-id} While it is not necessary that an authorization code is a JWT, it is easier to implement that way. It is an authorization for @var{client-id}, an URI identifying a client, to access the data of the user identified by @var{webid}, an URI too. It should only be valid for a limited amount of time, and used once only. The DPoP proof is a token that is issued by the client, and presented to the resource server along with an access token. It is only valid for one request, and for one use. So, it should have a very short validity frame, for instance 30 seconds, and should only be valid for a specific request method @var{htm} and a specific request URI @var{htu}, down to the path, but ignoring the query and fragment. The DPoP proof is the proof of possession of @var{jwk}, a public key. It should always have a @var{typ} field set to @code{"dpop+jwt"}. To construct an authorization code, you would either need @code{#:@var{jwt-header}} and @code{#:@var{jwt-payload}}, as for any token, or a combination of parameters: @itemize @item @code{#:@var{alg}} or @code{#:@var{signing-key}}, to initialize a JWT; @item @code{#:@var{iat}} and @code{#:@var{exp}} or @code{#:@var{validity}}, because it is issued for a limited time window (around 30 seconds); @item @code{#:@var{nonce}}, because it is single-use; @item @code{#:@var{webid}}, the user identifier; @item @code{#:@var{client-id}}, the client identifier. @end itemize The authorization code is signed and verified by the same entity. So, the key lookup function is tuned to always return the issuer key. You need to set it as the @code{#:@var{issuer-key}} keyword argument of the @code{decode} function. @end deftp @deffn {Generic} webid @var{token} Return the user identifier in @var{token}, as an URI. @end deffn @deffn {Generic} client-id @var{token} Return the client identifier in @var{token}, as an URI. @end deffn @deftp {Exception type} &invalid-authorization-code This exception is raised when the authorization ccode is invalid. @end deftp @deffn {function} make-invalid-authorization-code Construct an exception of type @code{&invalid-authorization-code}. @end deffn @deffn {function} invalid-authorization-code? @var{exception} Check whether @var{exception} was raised because of an invalid authorization code. @end deffn @node Caching on server side @chapter Caching on server side Both the identity provider and the resource server need to cache things. The identity provider will cache application webids, and the resource server will cache the identity provider keys, for instance. The solution is to use a file-system cache. Every response (except those that have a cache-control policy of no-store) are stored to a sub-directory of @emph{XDG_CACHE_HOME}. Each store has a 5% chance of triggering a cleanup of the cache. When a cleanup occurs, each cached response has a 5% chance of being dropped, including responses that are indicated as valid. This way, a malicious cache response that has a maliciously long validity will not stay too long in the cache. A log line will indicate which items are dropped. The @emph{(webid-oidc cache)} module exports two functions to deal with the cache. @deffn function clean-cache @var{[#percents]} Drop @var{percents}% of the cache right now. @end deffn @deffn function use-cache @var{f} Call @var{f} with no arguments, with the default HTTP request method set to a function that tries to use the cache first.o The cache will be read and written in the @samp{web-cache} subdirectory of the cache home. To check the time window validity, the @var{current-date} parameter is used. The back-end function, @var{http-get}, defaults to that of @emph{(web client)}. @end deffn @deffn parameter cache-home This parameters sets the cache directory. By default, it is @emph{XDG_CACHE_HOME}. @end deffn @node The HTTP Link header @chapter The HTTP Link header The HTTP Link header lets you attach metadata about a resource, directly in the HTTP protocol. It is used to link resources to their auxiliary resources, for instance. The following API is defined in @emph{(webid-oidc http-link)}: @deftp {Class} @var{target-iri} @var{relation-type} @var{target-attributes} The link refers to the @var{target-iri} that is being linked to the requested resource, with a given @var{relation-type} (a string), and optional additional @var{target-attributes}. When constructing a , you should use the @code{#:@var{target-iri}}, @code{#:@var{relation-type}} and @code{#:@var{target-attributes}} keyword arguments (@code{#:@var{target-attributes}} defaults to the empty list) to initialize the link. For convenience, the @code{#:@var{anchor}}, @code{#:@var{hreflang}}, @code{#:@var{media}}, @code{#:@var{title}}, @code{#:@var{title*}} and @code{#:@var{type}} keyword arguments can be passed to add well-known target attributes. @end deftp @deftp {Class} @var{key} @var{value} If you wish to add new extension target attributes, you can create an ad-hoc target attribute with @var{key} and @var{value} (initialized as @code{#:@var{key}} and @code{#:@var{value}} constructor keyword arguments). @end deftp @deffn {Generic} target-iri @var{link} @deffnx {Generic} relation-type @var{link} @deffnx {Generic} target-attributes @var{link} Getters for the @code{} class. @end deffn @deffn {Generic} key @var{target-attribute} @deffnx {Generic} value @var{target-attribute} Getters for the @code{} class. @end deffn @deffn {Generic} target-attribute @var{link} @var{key} Return the value of the first target attributet with @var{key}. @end deffn @deffn {Generic} anchor @var{link} @deffnx {Generic} hreflang @var{link} @deffnx {Generic} media @var{link} @deffnx {Generic} title @var{link} @deffnx {Generic} title* @var{link} @deffnx {Generic} type @var{link} Convenience attribute lookup functions. @code{anchor} returns an URI referencce, the others return a string. @end deffn @deffn {function} declare-link-header! Declare functions to parse, validate and print HTTP Link headers with the Guile web request / response API. @end deffn @deffn {function} request-links @var{request} @deffnx {function} response-links @var{response} Return the list of links in @var{request} or @var{response}. @end deffn @node Content negociation @chapter Content negociation There are a number of different available syntaxes for RDF, some being simple and human readable like @emph{turtle}, and others more adapted to the JavaScript ecosystem like @emph{json-ld}. To help clients both from and outside of the JS ecosystem, the server needs to perform @dfn{content negociation}, i.e. convert from one content-type to another. @deffn {function from @code{(webid-oidc serve)}} convert @var{client-accepts} @var{server-name} @var{path} @var{content-type} @var{content} Convert the resource representation under @var{path} on @var{server-name}, which has a given @var{content-type} and @var{content}, to a content-type that the @var{client accepts}. Return 2 values: @enumerate @item the accepted content-type; @item the content in the given content-type. @end enumerate Currently, the only conversions are from and to @emph{Turtle} and @emph{N-Quads}. @end deffn @node Server endpoints @chapter Server endpoints The disfluid server consists of a set of endpoints that handle requests. The @emph{(webid-oidc server endpoint)} module defines the base building blocks. @deftp {Class} () @var{host} @var{path} All endpoints define a @var{host} for which they are relevant, and an absolute @var{path}. If a request comes with a matching host and a matching path prefix, then it will be handled by this endpoint. If @var{host} is @code{#f}, then this endpoint will be used for all hosts. You can construct an endpoint with the @code{#:@var{host}} and @code{#:@var{path}} keyword arguments: the former is a string (defaults to @code{#f}), and the latter is a string starting with @code{"/"} (defaults to @code{"/"}). @end deftp @deffn {Generic} handle @var{endpoint} @var{request} @var{request-body} Handle @var{request} with @var{endpoint}. @var{request} is the request, in the form of an object from @emph{(web request)}. @var{request-body} is @code{#f}, a string or a bytevector. Return 3 values: a response, a response body (a string, bytevector, input port, or @code{#f} if no body is expected), and some response meta-data. @end deffn @deffn {Generic} host @var{endpoint} Return the host name @var{endpoint} is configured to respond to. @end deffn @deffn {Generic} path @var{endpoint} Return the path prefix @var{endpoint} is configured to respond to. @end deffn @deffn {Generic} relevant? @var{endpoint} @var{request} Check if @var{endpoint} is configured to respond to @var{request}. @end deffn The handler may throw exceptions to signal errors. Exception messages will be printed to the log file, and user messages will be passed to the user. @menu * Error signalling:: * Router endpoint:: * Request authentication:: * Hello world:: * Reverse proxy:: * Client pages:: * Identity provider:: * Resource server:: @end menu @node Error signalling @section Error signalling The @emph{(webid-oidc server endpoint)} module defines exception types that can be emitted to abort the computation in a handler. If an exception of a different kind is raised, this will lead to a 500 Internal Server Error response. @deftp {Exception type} &web-exception @var{code} @var{reason-phrase} The request failed, with @var{code} and @var{reason-phrase}. @end deftp @deffn {function} make-web-exception @var{code} @var{reason-phrase} Create an exception with @var{code} and @var{reason-phrase}. @end deffn @deffn {function} web-exception? @var{exn} Check if @var{exn} was thrown because the request failed. @end deffn @deffn {function} web-exception-code @var{exn} @deffnx {function} web-exception-reason-phrase @var{exn} Return the code and reason-phrase for when @var{exn} was thrown, if it was thrown because of a failing request. @end deffn @deftp {Exception type} &caused-by-user @var{webid} If a web exception is raised, maybe it is caused by some user identified by @var{webid} (an URI, or @code{#f}. @end deftp @deffn {function} make-caused-by-user @var{webid} Constructor for @code{&caused-by-user}. @end deffn @deffn {function} caused-by-user? @var{exn} Check if @var{exn} was caused by the user. @end deffn @deffn {function} caused-by-user-webid @var{exn} Return the webid of the user that caused @var{exn}. @end deffn @deftp {Exception type} &user-message @var{sxml} An exception containing a message that is safe to show to the user, as an SXML fragment of XHTML. Typically, this would be a @code{

}, or a @code{

}. You can set a user-message multiple times. The occurences will be concatenated in the response, in the order they appear in the composite exception. @end deftp @deffn {function} make-user-message @var{sxml} Create a new user message containing the @var{sxml} fragment. @end deffn @deffn {function} user-message? @var{exn} Check if there is at least one user message in @var{exn}. @end deffn @deffn {function} user-message-sxml @var{exn} Return all user messages in @var{exn}, as a @code{
} SXML fragment. @end deffn @node Router endpoint @section Router endpoint The first non-trivial handler is for the router endpoint, defined in @emph{(webid-oidc server endpoint)}. @deftp {Class} () @var{routed} The router has a list of endpoints, and chooses which one will handle an incoming request based on the request fields. The @var{routed} endpoints is a list of endpoints. You can set it at construction time with @code{#:@var{routed}}. The router will check if the @var{routed} endpoints are relevant, in turn, or return a 404 Not Found response if no endpoint is relevant. @end deftp @deffn {Generic} routed @var{router} Return the list of endpoints for @var{router}. @end deffn @node Request authentication @section Request authentication The @emph{(webid-oidc server endpoint authentication)} defines an endpoint that authentifies the user and passes the annotated request to a backend endpoint. @deftp {Class} () @var{backend} @var{server-uri} The authenticator calls the @var{backend} endpoint once it has authentified the user. If the authentication is successful, the request is annotated with a @code{'user} entry in the alist table containing the URI of the user. Otherwise, it is passed as is. To check the validity of the DPoP proof, the endpoint must know the public name of the server that is running, @var{server-uri}. It can be constructed with the @code{#:@var{backend}} and @code{#:@var{server-uri}} keyword arguments, respectively an endpoint and an URI. @end deftp @deffn {Generic} backend @var{authenticator} Return the backend endpoint of @var{authenticator}. @end deffn @deffn {Generic} server-uri @var{authenticator} Return the public server URI of @var{authenticator}. @end deffn @node Hello world @section Hello world The @emph{(webid-oidc server endpoint hello)} module defines an endpoint that will greet the user, to check that Solid authentication worked. It is intended to be a backend for an authenticator. @deftp {Class} () An endpoint that will greet anonymous users and authenticated users. @end deftp @node Reverse proxy @section Reverse proxy The @emph{(webid-oidc server endpoint reverse-proxy)} module defines a @dfn{reverse proxy}, an endpoint that passes the incoming request to a backend server with added metadata. @deftp {Class} () @var{backend-uri} @var{authentication-header} This endpoint will handle the incoming requests by adding a header, named @var{authentication-header} (a symbol), to hold the webid of the authentified user, and passing it to the server listening at @var{backend-uri} (an URI). You can construct it with @code{#:@var{backend-uri}} and @code{#:@var{authentication-header}}. @end deftp @deffn {Generic} backend-uri @var{reverse-proxy} Return the URI where requests are passed. @end deffn @deffn {Generic} authentication-header @var{reverse-proxy} Return the header set by the reverse proxy to hold the authenticated webid. @end deffn @node Client pages @section Client pages The @emph{(webid-oidc server endpoint client)} module defines an endpoint to serve the public pages for a client application. @deftp {Class} () @var{client-id} @var{redirect-uris} @var{client-name} @var{client-uri} @var{grant-types} @var{response-types} During the OIDC authorization process, the identity provider must check some things against the public URI of a client application. This endpoint will respond to this query. You can construct it with @code{#:@var{redirect-uris}} (a list of URIs), @code{#:@var{client-id}} (an URI, or string encoding an URI), @code{#:@var{client-name}} (a string), @code{#:@var{grant-types}} (a list of symbols or strings), @code{#:@var{response-types}} (a list of symbols or strings). @end deftp @deffn {Generic} redirect-uris @var{client-id} Return the list of approved redirection URIs. @end deffn @deffn {Generic} client-id @var{client-id} Return the URI where the application can be queried by the identity provider. @end deffn @deffn {Generic} client-name @var{client-id} Return the associated name. Please note that the companion implementation of the identity provider in this package will not display the name to the user, because it can be misleading. @end deffn @deffn {Generic} client-uri @var{client-id} Return the URI where people can find information about the application. Also not hidden by the identity provider. @end deffn @deftp {Class} () This endpoint receives an authorization code, and display it to the user, asking to paste it in the application. @end deftp @node Identity provider @section Identity provider The @emph{(webid-oidc server endpoint identity-provider)} module defines endpoints that are required for an identity provider. @deftp {Class} () @var{configuration} Serve the OIDC @var{configuration}. You can construct it with @code{#:@var{configuration}}. @end deftp @deffn {Generic} configuration @var{endpoint} Return the OIDC configuration served by @var{endpoint}. @end deffn @deftp {Class} () @var{subject} @var{encrypted-password} @var{key-file} The authorization endpoint prompts the user for a password, and then grants an authorization code. It is defined for one particular user, whose webid is @var{subject}, and who knows the password. The authorization endpoint signs authorization codes with the key under @var{key-file}. If this file does not exist, a new key will be generated. The constructor expects keyword arguments @code{#:@var{subject}}, @code{#:@var{encrypted-password}} and @code{#:@var{key-file}}. @end deftp @deffn {Generic} subject @var{authorization-endpoint} Return the webid of the user authorized by @var{authorization-endpoint}. @end deffn @deffn {Generic} encrypted-password @var{authorization-endpoint} Return the encrypted password used to authentify the user at @var{authorization-endpoint}. @end deffn @deffn {Generic} key-file @var{authorization-endpoint} Return the file name where the key to sign authorization codes in @var{authorization-endpoint} is stored. @end deffn @deftp {Class} () @var{issuer} @var{key-file} The token endpoint exchanges authorization codes or refresh tokens for new access tokens. The access token is signed with the key loaded from @var{key-file}, and the access token is bound to the @var{issuer} URI (host name). You can construct a token endpoint with the @code{#:@var{issuer}} and @code{#:@var{key-file}} keyword arguments. @end deftp @deffn {Generic} issuer @var{token-endpoint} Return the issuer (URI with no path) that this @var{token-endpoint} operates for. @end deffn @deffn {Generic} key-file @var{token-endpoint} Return the file name where the key to sign access tokens in @var{token-endpoint} is stored. @end deffn @deftp {Class} () @var{key-file} The JWKS endpoint returns the list of valid public keys used by the identity provider. For now, only the public part of the key under @var{key-file} is served. You can construct one with the @code{#:@var{key-file}} header argument. @end deftp @deftp {Class} () @var{oidc-discovery} @var{authorization-endpoint} @var{token-endpoint} @var{jwks-endpoint} @var{default} An identity provider is the sum of an @var{OIDC discovery} endpoint, an @var{authorization-endpoint}, an @var{token-endpoint} and an @var{jwks-endpoint}, and a @var{default} endpoint that gets all the requests that aren’t handled by the identity provider. You can construct one with the following keyword arguments: @code{#:@var{authorization-endpoint}}, @code{#:@var{token-endpoint}}, @code{#:@var{jwks-endpoint}} and @code{#:@var{default}}. @end deftp @deffn {Generic} oidc-discovery @var{identity-provider} Return the OIDC discovery endpoint of the @var{identity-provider}. @end deffn @deffn {Generic} authorization-endpoint @var{identity-provider} Return the authorization endpoint of the @var{identity-provider}. @end deffn @deffn {Generic} token-endpoint @var{identity-provider} Return the token endpoint of the @var{identity-provider}. @end deffn @deffn {Generic} jwks-endpoint @var{identity-provider} Return the JWKS endpoint of the @var{identity-provider}. @end deffn @deffn {Generic} default @var{identity-provider} Return the endpoint where all requests that aren’t handled by any element of the @var{identity-provider} go. @end deffn @node Resource server @section Resource server The resource server is a read-write server with fine-grained authorizations. You can create one in the @emph{(webid-oidc server endpoint resource-server)} module. @deftp {Class} () @var{server-uri} @var{owner} @var{data-home} Create a resource server endpoint. To manage RDF data, and in particular to identify owned resources, it is necessary that the server knows its public @var{server-uri}. @var{owner} is the webid of someone that has total control. If you want to manage multiple resource servers, you must make sure that each one of them has a separate @var{data-home} directory. You can construct one with @code{#:@var{server-uri}} (an URI), @code{#:@var{owner}} (an URI) and @code{#:@var{data-home}} (a directory file name or a thunk returning a file name; it may exist or not, defaults to @code{$XDG_CACHE_HOME}). @end deftp @deffn {Generic} server-uri @var{resource-server} Return the public URI of the @var{resource-server}. @end deffn @deffn {Generic} owner @var{resource-server} Return the webid of a user that has full control over @var{resource-server}. @end deffn @deffn {Generic} data-home @var{resource-server} Return the directory where @var{resource-server} stores persistent data. @end deffn @node Resources stored on the server @chapter Resources stored on the server To store and serve resources, the server has two distinct mechanisms. A @dfn{content} is a read-only possible value for a resource, indexed by etags, and a @dfn{path} is a mutable value that indicates the etag of the resource, and of the auxiliary resources (description and ACL). With this separation, it is possible to atomically delete a resource and all associated auxiliary resources, by unlinking the corresponding @emph{path}. It is also possible to mutate separately the ACL and the resource itself without writing a copy for both. The @emph{content} API is contained in the @code{(webid-oidc server resource content)} module. @deftp {Class} () @var{etag} @var{content-type} @var{contained} @var{static-content} This class encapsulate a static resource content linked to a particular @var{etag}. The @var{content-type} is a symbol, and @var{static-content} is a bytevector, although a string will be encoded to UTF-8 at construction time. @var{contained} is either @code{#f}, if the resource is not a container, or a list of resource paths (each one is a string) identifying contained resources. You can construct a content in two ways. If you pass @code{#:@var{etag}}, it will be loaded from the file system under the @var{etag} index, or if @code{#:@var{cache}} is passed or the @code{current-content-cache} is set to @var{cache}, it will try to load from @var{cache} first. If you define a cache, the result will also be added to @var{cache}. If you pass @code{#:@var{content-type}}, @code{#:@var{contained}} and @code{#:@var{static-content}}, but not @code{#:etag}, it will be created and saved to disk, and optionally added to the @code{#:@var{cache}} or the current content cache. @end deftp @deftp {Class} () @var{cache} Since the contents are read-only, it is possible to cache the values in memory to avoid reading the same file more than once. This is how the session works. @var{cache} is a hash table for string etag values to cached content values. It is initialized as an empty hash table. @end deftp @deffn {Generic} etag @var{content} Return the ETag of @var{content}, as a string. @end deffn @deffn {Generic} content-type @var{content} Return the Content-Type of @var{content}, as a symbol. @end deffn @deffn {Generic} contained @var{content} Return the contained paths of @var{content}, as a list of strings, or @code{#f} if it is not a container. @end deffn @deffn {Generic} static-content @var{content} Return the static content of @var{content}, as a bytevector. @end deffn @deffn {Generic} delete-content @var{content} @deffnx {Generic} delete-content @var{etag} Remove [@var{content}’s] @var{etag} from the file system. If it is cached in @var{session}, also remove the cached value. Otherwise, other sessions can still access it. @end deffn @deffn parameter current-content-cache A guile parameter indicating a cache where loaded contents should be added and preferably fetched. By default, no caching is performed. You need to set this parameter to benefit from it. @end deffn The @emph{path} API is defined in @code{(webid-oidc server resource path)}. @deffn function read-path @var{path} Read the resource at @var{path}, and return 2 values: @enumerate @item the content of the main resource; @item an alist where keys are auxiliary resource type URIs (the type is from @code{(web uri)}), and the values are contents of the corresponding resource. @end enumerate If the resource is not found, raise an exception with type @code{&path-not-found}, and maybe @code{&uri-slash-semantics-error} if a resource with a different ending-in-slash exists. If the @code{current-content-cache} parameter is set to a cache, it will be used to load the content and auxiliary contents. This function is safe to call when the path is being modified, either by another thread, process or else, as the returned values will always be consistent. However, once the function returns, an updating process may have deleted the returned content. If this is the case, then you must call this function again to read the updated path. @end deffn @deffn function update-path @var{path} @var{f} [@code{#:@var{create-intermediate-containers?}}=@code{#f}] Read @var{path}, call @var{f} with two values: the main content and the auxiliary contents (as returned by @var{read-path}), and update the path accordingly. If @var{path} does not exist, then the first argument is @code{#f} and the second one is the empty list. If @var{f} returns @code{#f}, then the resource is deleted. If @var{f} returns two values: a content as the first and an alist of auxiliary types (as URIs) to auxiliary contents as the second, then the resource is updated. This function uses the @code{current-content-cache} parameter to load contents. If a resource is created or deleted, the parent’s containment triples will be modified, so they will also be loaded in the cache. Some operations should create the intermediate containers for a given path, this is the case for the @code{PUT} HTTP verb. For @code{POST}, the parent should exist. The @var{#:create-intermediate-containers?} switch lets you change the behavior. In any case, it is an error to delete a non-empty container. The update is atomic, meaning that at any point in time the file is fully written out. Concurrent access to the same resource is performed by locking the lock file named @var{X}/.lock, where @var{X} is the first character of the base64-url sha-256 hash of the path. @strong{The lock file is not meant to be removed} when the resource is unlocked. It should be locked with @code{flock} instead. @strong{Like other forms of lock-based synchronization, this function is not composable}. This means that you cannot call this function within @var{f}, otherwise a deadlock may ensue. If the resource is created or deleted, then the parent resource is updated as well. To avoid deadlocks with other processes, please follow the following rules: lock the path, then lock the parent path, then update the parent, then unlock the parent, and finally unlock the child path. @end deffn The Web Access Control specification defines an RDF vocabulary to check whether a given user is allowed to perform some operations. The @code{(webid-oidc server resource wac)} helps you do that. @deffn function wac-get-modes @var{server-name} @var{path} @var{user} @var{[#:http-get]} Return the list of modes that are allowed for @var{user} accessing @var{path}. The @var{server-name} URI is required to find the relevant triples in the ACL. If @var{user} is unauthenticated, pass @code{#f}. Please note that in any case, the data owner should have all rights whatsoever, bypassing WAC. Otherwise, it is possible to steal control away from the data owner. @end deffn @deffn function check-acl-can-read @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-write @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-append @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} @deffnx function check-acl-can-control @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} Assert that the resource at @var{path} on @var{server-name} is owned by @var{owner}, and check that @var{user} has the proper authorization. Otherwise, raise an exception of type @code{&forbidden}. @end deffn @node Running a client @chapter Running a client The job of the client is to use accounts to fetch private resources on the web. The @emph{(webid-oidc client)} defines the @code{} class. @deftp {Class} @var{client-id} @var{key-pair} @var{redirect-uri} In OIDC, a client is an application that does not hold the resources. It may in fact be a network server available on the web, or a program that you run on your machine. Being a network server or not is irrelevant. The @code{} class is designed with immutability in mind. You can create a client with the @code{make} generic method, using these keywords to initialize values: @table @code @item #:client-id to set the public client identifier (this endpoint should be available on the world-wide web), as a string representing an URI or an URI from @code{(web uri)}; @item #:key-pair to use a specific key pair. If not set, a new key pair will be generated; @item #:redirect-uri to set the redirect URI that the application controls. It may just be a page showing the authorization code, with instructions on how to paste this code into the application. It should match one of the authorized redirect URIs in the client identifier endpoint. If you want to set a state parameter for the redirection, you can do it by setting the guile parameter @code{authorization-state}. @end table @end deftp @deftypefn {Generic method} uri client-id (@var{client} @code{}) @deftypefnx {Generic method} {key pair} key-pair (@var{client} @code{}) @deftypefnx {Generic method} uri redirect-uri (@var{client} @code{}) Slot accessors for @var{client}. @end deftypefn @defvr {Parameter} client Define this parameter to set the client to use to access private data. @end defvr To access private data, you must identify yourself. The @emph{(webid-oidc client accounts)} module lets you define accounts. @deftp {Class} @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{key-pair} Encapsulate an account. @var{subject} is your webid, while @var{issuer} is a host name. @var{id-token} is the @emph{decoded} OIDC ID token, i.e. a pair of @code{(header . payload)}, because we don’t need to show it to any other party, so its authenticity needs not be demonstrated. However, @var{access-token} is an @emph{encoded} access token (into a string), because we don’t need to worry about its internals on client side. There are different ways to initialize an account. First, you can save all parameters to some form of storage, and restore it by using the associated keyword arguments at construction time: @table @code @item #:subject @item #:issuer @item #:id-token @item #:access-token @item #:refresh-token @item #:key-pair @end table If you want to make a new account, you would ask the user for an identity provider, and pass it with @code{#:issuer} as the only initialized value. The constructor will log you in, using the @code{authorization-process} and @code{anonymous-http-request} function parameters. If you want to refresh an access token, you would also set @code{#:refresh-token}. In any case, when you don’t specify a value, it’s as if you passed @code{#f}. @end deftp @defvr {Parameter} authorization-process This function is called when an explicit user authorization is required, for instance because there is no refresh token and the access token expired. The function takes an URI as argument, with an additional @code{#:reason} keyword argument containing the reason for the authorization as a string. In this function, you should present the reason to the user and ask the user to browse this URI so that your application gets the authorization code. @end defvr @defvr {Parameter} anonymous-http-request This function is used as a back-end for private resource access, and to query the server configuration. It defaults to @code{http-request} from @emph{(web client)}. @end defvr @deftypefn {Generic method} uri subject (@var{account} @code{}) @deftypefnx {Generic method} set-subject (@var{account} @code{}) (@var{uri} {string or URI}) @deftypefnx {Generic method} uri issuer (@var{account} @code{}) @deftypefnx {Generic method} set-issuer (@var{account} @code{}) (@var{uri} {string or URI}) @deftypefnx {Generic method} {optional decoded ID token} id-token (@var{account} @code{}) @deftypefnx {Generic method} set-id-token (@var{account} @code{}) (@var{id-token} {optional ID token}) @deftypefnx {Generic method} {optional encoded access token} access-token (@var{account} @code{}) @deftypefnx {Generic method} set-access-token (@var{account} @code{}) (@var{access-token} {optional access token}) @deftypefnx {Generic method} {optional } refresh-token (@var{account} @code{}) @deftypefnx {Generic method} set-refresh-token (@var{account} @code{}) (@var{refresh-token} {optional }) @deftypefnx {Generic method} {key pair} key-pair (@var{account} @code{}) @deftypefnx {Generic method} set-key-pair (@var{account} @code{}) (@var{key-pair} {optional key pair}) Slot accessors and functional setters for @var{account}. @end deftypefn If you intend to run a public network server as a client application, you may have multiple different users, but you should not let any user use any account. If this is the case, you can either store the accounts on the user agent storage (for instance, as a cookie), or store all of them on the server. If you choose to store the accounts on the user agent, at least use a new key pair for each of them. If you want to store the user database on the server side, be aware that no entity other than yourself will check that your user abides by any term of service, so it is possible that a single user makes a lot of accounts to annoy you and fill your hard drive with key pairs. If your application does not let random people to use it, you might want to use @emph{protected accounts}, to help you check that the users cannot impersonate each other. @deftp {Class} (@code{}) @var{username} @var{encrypted-password} This superclass of @code{} is protected by a username and password. It is constructed with the initializer keywords @code{#:username} and @code{#:encrypted-password}. @end deftp @deftypefn {Generic method} username (@var{protected-account} @code{}) @deftypefnx {Generic method} set-username (@var{protected-account} @code{}) (@var{username} ) @deftypefnx {Generic method} encrypted-password (@var{protected-account} @code{}) @deftypefnx {Generic method} set-encrypted-password (@var{protected-account} @code{}) (@var{encrypted-password} ) Slot accessors and functional setters for @var{protected-account}. @end deftypefn @deftypefn {Generic method} invalidate-access-token (@var{account} @code{}) Indicate that the access token in @var{account} cannot be used. Before using @var{account} again, you will need to refresh the access token. This function does not mutate @var{account}. @end deftypefn @deftypefn {Generic method} invalidate-refresh-token (@var{account} @code{}) Indicate that the refresh token has been revoked for @var{account}. This is usually an indication that the user don’t want your application to access her private data. This function does not mutate @var{account}. @end deftypefn @deftypefn {Generic method} refresh (@var{account} @code{}) Refresh the access token. @end deftypefn @deftp {Exception type} &authorization-code-required @var{uri} If the login process requires the user to send an authorization code, an exception of this type will be raised, with an implicit invitation for the user to browse @var{uri} and follow the instructions. The instructions will be handled by the @var{redirect-uri} in the @code{login} function. If your client is a traditional web application, the user will be redirected to this URI with an authorization code. If your client is a native application, then maybe that redirection URI should display the authorization code and invite the user to paste it in the appropriate place in the application. When an exception of this type is raised during the @code{login} function, it is continuable, meaning that the login function will resume. You need to create an exception handler for an exception of this type, look up the @var{uri}, direct the user to browse it, get the authorization code back, and @emph{return} the authorization code @emph{from the exception handler}. @end deftp @deffn function make-authorization-code-required @var{uri} @deffnx function authorization-code-required? @var{error} @deffnx function authorization-code-required-uri @var{error} Constructor, predicate, and accessor for the @code{&authorization-code-required} exception type. @end deffn @deftp {Exception type} &refresh-token-expired The refresh token can be used to still perform requests on behalf of the user when perse is offline. However, if the refresh token expires while the user is offline, it is not possible to log in again, because it requires a new authorization code. So, it is not possible to recover from this error, and the refresh token is immediately discarded. @end deftp @deffn function make-refresh-token-expired @deffnx function refresh-token-expired? @var{error} Constructor and predicate for the @code{&refresh-token-expired} exception type. @end deffn @deffn function invalidate-access-token @var{account} Discard the access token for @var{account}. It is not saved in the user database yet. This is roughly equivalent to log out. @end deffn @deffn function invalidate-refresh-token @var{account} Discard the refresh token for @var{account}. You still need to save the @var{account}. @end deffn @deftp {Exception type} &token-request-failed @var{response} @var{response-body} If the token endpoint is unable to deliver an identity token and an access token, this exception is raised with the identity provider @var{response} and @var{response body}. This exception cannot be continued. @end deftp @deffn function make-token-request-failed @var{response response-body} @deffnx function token-request-failed? @var{error} @deffnx function token-request-response @var{error} @deffnx function token-request-response-body @var{error} Constructor, predicate, and accessors for the @code{&token-request-failed} exception type. @end deffn The @emph{(webid-oidc client)} module provides the most useful function for a client. @deffn function request @var{account} @var{uri} . @var{args} Perform a request on behalf of @var{account}, with the current value of the @var{client} parameter as the client, using as a backend the current value of @var{anonymous-http-request}. @end deffn Finally, to implement your application, there needs to be a public endpoint for the resource server to check that you are not impersonating another application. This endpoint can be served by any web server, but a convenience procedure is made available here: @deffn function serve-application @var{id} @var{redirect-uri} @var{[#client-name]} @var{[#client-uri]} Return a handler for web requests to serve the application manifest and the redirection to transmit the authorization code. You should set the @var{client-name} to your application name and @var{client-uri} to point to where to a presentation of your application. @end deffn @node Serialization to (S)XML @chapter Serialization to (S)XML The @emph{(webid-oidc serializable)} module provides tools to have serialization to SXML and deserialization from XML. @deftp {Class} () @var{module-name} @var{direct-name} This metaclass permits to register plugins. @var{module-name} is the name of a module that defines the class, and @var{direct-name} is the class name without the surrounding angle brackets. Please note that all plugin classes should be surrounded by angle brackets. Most GOOPS classes defined in this program are actually plugin classes. Serialization works for each slot by serializing other plugin classes the normal way, and other values are simply represented as strings with @code{display}. Deserialization works by loading the module containing the target class, collecting a value for each slot (a string for non-plugin-class-valued slots), and making an instance of that class with all collected values. The initialization function should accept strings values, for objects that are not of a plugin class. Since most scheme data types written by @code{display} cannot be read in a meaningful way, you may add a @code{#:->sxml} slot option with a function taking the slot value and either returning a string that the initialization function can parse, or an SXML fragment. For instance, if a slot should contain an URI value, you would pass @code{#:->sxml uri->string} as options to the slot definition, and accept a string value in the initialization function, that you would convert to an URI with @code{string->uri}. Sometimes slots contain functional data that cannot be serialized. In this case, pass @code{#:->sxml 'ignore} to avoid serialization. @end deftp @deffn {function} read/xml @var{port} Read the XML document at @var{port} and deserialize it. @end deffn @deffn {function} ->sxml @var{object} Convert @var{object} to an SXML fragment. @end deffn @node Exceptional conditions @chapter Exceptional conditions The library will raise an exception whenever something fishy occurs. For instance, if a signature is invalid, or the expiration date has passed. When the client is responsible for an error, such as presenting an invalid access token, a compound exception is raised. It is sometimes useful for the user to understand what happened, because it could indicate a problem in a part of the web they need to change. For instance, if the access token cannot be decoded because the identity provider is down, then maybe informing the user of that fact is useful. However, presenting too much information is a security risk. For instance, if the system administrator also runs a private server on the same machine, and a malicious client tries to pretend that this private server is an identity provider, then the public server will try to query the private server. If an error happens and the public server displays some information to the client, then a part of the information comes from the private server. Thus, a balance needs to be found so that not too much is revealed. The module @emph{(webid-oidc errors)} defines an exception type that indicates a message that is safe to display to the user. @deftp {Exception type} &message-for-the-user @var{message} Indicate that @var{message} can be safely displayed to the user. It is an XHTML paragraph (or equivalent), presented as SXML. @end deftp @deffn function make-message-for-the-user @var{message} @deffnx user-message @var{exception} Constructor and accessor for the @code{&message-for-the-user} exception type. @end deffn @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @node Index @unnumbered Index @printindex cp @bye