summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-05-11 09:28:57 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-05-11 09:49:58 +0200
commit2f3fd2ed58a2376fce8b5ca6175eada279bc001f (patch)
tree021fbec962ac734d32093ccf7c759aa1d90a1a14
parentc056c6980f5d8ff954187dcd2571ffeb8fae60b5 (diff)
Use the texinfo markup for the manual
-rwxr-xr-xbootstrap4
-rw-r--r--configure.ac1
-rw-r--r--doc/Makefile.am9
-rw-r--r--doc/html2texi.xsl367
-rw-r--r--doc/manual.html1337
-rw-r--r--doc/webid-oidc.texi821
-rw-r--r--guix/vkraus/packages/webid-oidc.scm3
7 files changed, 823 insertions, 1719 deletions
diff --git a/bootstrap b/bootstrap
index 07f8c35..c04994d 100755
--- a/bootstrap
+++ b/bootstrap
@@ -1,10 +1,6 @@
#!/bin/sh
-(echo '\input texinfo' ;
- echo '@include version.texi') \
- > doc/webid-oidc.texi || exit 1
autoreconf -vif || exit 1
-rm doc/webid-oidc.texi || exit 1
sed -i 's|SHELL = /bin/sh|SHELL = @SHELL@|g' po/Makefile.in.in || exit 1
## Prepare the man pages
diff --git a/configure.ac b/configure.ac
index eb9235f..0327beb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,7 +10,6 @@ AM_CONDITIONAL([INDENT_CHECK_ENABLED], [test "x$enable_indent_check" = "xyes"])
AC_PROG_CC
AM_MISSING_PROG([SNARF], [guile-snarf])
AM_MISSING_PROG([HELP2MAN], [help2man])
-AM_MISSING_PROG([XSLTPROC], [xsltproc])
AM_MISSING_PROG([INDENT], [indent])
LT_INIT
AC_CONFIG_HEADERS([config.h])
diff --git a/doc/Makefile.am b/doc/Makefile.am
index eae1187..c339dc2 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,14 +1,7 @@
-EXTRA_DIST += %reldir%/html2texi.xsl \
- %reldir%/manual.html \
- %reldir%/webid-oidc.texi \
+EXTRA_DIST += \
%reldir%/fdl.texi
info_TEXINFOS = %reldir%/webid-oidc.texi
%canon_reldir%_webid_oidc_texi_TEXINFOS = %reldir%/fdl.texi
AM_MAKEINFOHTMLFLAGS = --css-include=doc/style.css
-
-%reldir%/Makefile.am: %reldir%/webid-oidc.texi
-
-$(srcdir)/%reldir%/webid-oidc.texi: %reldir%/html2texi.xsl %reldir%/manual.html
- $(AM_V_GEN) $(XSLTPROC) $^ > $@-t && mv $@-t $@
diff --git a/doc/html2texi.xsl b/doc/html2texi.xsl
deleted file mode 100644
index dc432c6..0000000
--- a/doc/html2texi.xsl
+++ /dev/null
@@ -1,367 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<xsl:stylesheet
- version="1.0"
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:html="http://www.w3.org/1999/xhtml"
- xmlns:info="http://planete-kraus.eu/ns/info">
-
- <xsl:output method="text"/>
-
- <xsl:template match="html:html">
- <xsl:text>\input texinfo @c -*-texinfo-*-&#xa;</xsl:text>
- <xsl:text>@comment $Id@w{$}&#xa;</xsl:text>
- <xsl:if test="@xml:lang">
- <xsl:text>@documentlanguage </xsl:text>
- <xsl:value-of select="@xml:lang" />
- <xsl:text>&#xa;</xsl:text>
- </xsl:if>
- <xsl:apply-templates select="html:head" />
- <xsl:text>@contents&#xa;</xsl:text>
- <xsl:apply-templates select="html:body" />
- <xsl:text>@bye&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:head">
- <xsl:text>@comment %**start of header&#xa;</xsl:text>
- <xsl:text>@include version.texi&#xa;</xsl:text>
- <xsl:text>@settitle </xsl:text>
- <xsl:apply-templates select="html:title" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@syncodeindex pg cp&#xa;</xsl:text>
- <xsl:text>@syncodeindex fn cp&#xa;</xsl:text>
- <xsl:text>@syncodeindex vr cp&#xa;</xsl:text>
- <xsl:text>@syncodeindex tp cp&#xa;</xsl:text>
- <xsl:text>@comment %**end of header&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates select="info:copying" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates select="info:dircategory" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates select="info:direntry" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@titlepage&#xa;</xsl:text>
- <xsl:text>@title </xsl:text>
- <xsl:apply-templates select="html:title" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@subtitle for version @value{VERSION}, @value{UPDATED}&#xa;</xsl:text>
- <xsl:text>@author </xsl:text>
- <xsl:apply-templates select="info:author" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@page&#xa;</xsl:text>
- <xsl:text>@vskip 0pt plus 1fill&#xa;</xsl:text>
- <xsl:text>@insertcopying&#xa;</xsl:text>
- <xsl:text>@end titlepage&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:copying">
- <xsl:text>@copying&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end copying&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:quotation">
- <xsl:text>@quotation&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end quotation&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:version">
- <xsl:text>@value{VERSION}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:updated">
- <xsl:text>@value{UPDATED}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:copyright-symbol">
- <xsl:text>@copyright{}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:dircategory">
- <xsl:text>@dircategory </xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:direntry">
- <xsl:text>@direntry&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end direntry&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:direntry-entry">
- <xsl:text>* </xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text>: (</xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text>)</xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:author">
- <xsl:apply-templates />
- </xsl:template>
-
- <xsl:template match="html:a[starts-with(@href, 'mailto:')]">
- <xsl:apply-templates />
- <xsl:text> (@email{</xsl:text>
- <xsl:variable name="address" select="substring-after(@href, 'mailto:')" />
- <xsl:value-of select="substring-before($address, '@')" />
- <xsl:text>@@</xsl:text>
- <xsl:value-of select="substring-after($address, '@')" />
- <xsl:text>})</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:body">
- <xsl:text>@ifnottex&#xa;</xsl:text>
- <xsl:text>@node Top&#xa;</xsl:text>
- <xsl:text>@top Webid-oidc&#xa;</xsl:text>
- <xsl:apply-templates select="child::*[not(self::html:h1) and count(preceding-sibling::html:h1) = 0]" />
- <xsl:text>@end ifnottex&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@menu&#xa;</xsl:text>
- <xsl:apply-templates mode="menu" select="html:h1" />
- <xsl:text>@end menu&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates select="child::*[self::html:h1 or count(preceding-sibling::html:h1) >= 1]" />
- </xsl:template>
-
- <xsl:template match="*" mode="menu">
- <xsl:text>* </xsl:text>
- <xsl:apply-templates />
- <xsl:text>::&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:h1">
- <xsl:text>@node </xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@</xsl:text>
- <xsl:choose>
- <xsl:when test="@type">
- <xsl:value-of select="@type" />
- </xsl:when>
- <xsl:otherwise>
- <xsl:text>chapter</xsl:text>
- </xsl:otherwise>
- </xsl:choose>
- <xsl:text> </xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- <xsl:variable name="chapter-number"
- select="count(preceding-sibling::html:h1) + 1" />
- <xsl:if test="count(following-sibling::html:h2[count(preceding-sibling::html:h1) = $chapter-number]) > 0">
- <!-- There are sub-sections; we need a menu -->
- <xsl:text>@menu&#xA;</xsl:text>
- <xsl:apply-templates mode="menu"
- select="following-sibling::html:h2[count(preceding-sibling::html:h1) = $chapter-number]" />
- <xsl:text>@end menu&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:if>
- </xsl:template>
-
- <xsl:template match="html:h2">
- <xsl:text>@node </xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>@section </xsl:text>
- <xsl:apply-templates />
- <xsl:text>&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="text()">
- <!-- Normalize the spaces, but keep one space before or after if
- needed -->
- <xsl:variable name="normalized" select="normalize-space(.)" />
- <xsl:variable name="first-char">
- <xsl:value-of select="substring(., 1, 1)" />
- </xsl:variable>
- <xsl:variable name="last-char">
- <xsl:value-of select="substring(., string-length(.))" />
- </xsl:variable>
- <xsl:variable name="first-char-space">
- <xsl:if test="$first-char = ' ' or $first-char = '&#x9;' or $first-char = '&#xA;'">
- <xsl:text> </xsl:text>
- </xsl:if>
- </xsl:variable>
- <xsl:variable name="last-char-space">
- <xsl:if test="$last-char = ' ' or $last-char = '&#x9;' or $last-char = '&#xA;'">
- <xsl:text> </xsl:text>
- </xsl:if>
- </xsl:variable>
- <xsl:variable name="space-left">
- <xsl:if test="count(preceding-sibling::*) >= 1 ">
- <xsl:value-of select="$first-char-space" />
- </xsl:if>
- </xsl:variable>
- <xsl:variable name="space-right">
- <xsl:if test="count(following-sibling::*) >= 1 ">
- <xsl:value-of select="$last-char-space" />
- </xsl:if>
- </xsl:variable>
- <xsl:if test="$normalized != ''">
- <xsl:value-of select="$space-left" />
- <xsl:value-of select="$normalized" />
- <xsl:value-of select="$space-right" />
- </xsl:if>
- </xsl:template>
-
- <xsl:template match="html:p[position() &lt; last()]">
- <!-- Leave an empty line -->
- <xsl:apply-templates />
- <xsl:text>@c fill&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:p">
- <xsl:apply-templates />
- <xsl:text>@c fill&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:dfn">
- <xsl:text>@dfn{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:pre">
- <xsl:text>@code{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:deffn">
- <xsl:text>@deffn </xsl:text>
- <xsl:value-of select="@type" />
- <xsl:text> </xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text> </xsl:text>
- <xsl:call-template name="arglist">
- <xsl:with-param name="arguments" select="@arguments" />
- </xsl:call-template>
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end deffn&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:deffnx">
- <xsl:text>@deffnx </xsl:text>
- <xsl:value-of select="@type" />
- <xsl:text> </xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text> </xsl:text>
- <xsl:call-template name="arglist">
- <xsl:with-param name="arguments" select="@arguments" />
- </xsl:call-template>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:defvr">
- <xsl:text>@defvr {</xsl:text>
- <xsl:value-of select="@type" />
- <xsl:text>} </xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end defvr&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:deftp">
- <xsl:text>@deftp {</xsl:text>
- <xsl:value-of select="@type" />
- <xsl:text>} </xsl:text>
- <xsl:value-of select="@name" />
- <xsl:text> </xsl:text>
- <xsl:call-template name="arglist">
- <xsl:with-param name="arguments" select="@arguments" />
- </xsl:call-template>
- <xsl:text>&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end deftp&#xa;</xsl:text>
- <xsl:text>&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:var">
- <xsl:call-template name="arglist">
- <xsl:with-param name="arguments">
- <xsl:apply-templates />
- </xsl:with-param>
- </xsl:call-template>
- </xsl:template>
-
- <xsl:template name="arglist">
- <xsl:param name="arguments" />
- <xsl:variable name="content" select="normalize-space($arguments)" />
- <xsl:choose>
- <xsl:when test="contains($content, ' ')">
- <xsl:call-template name="arglist">
- <xsl:with-param
- name="arguments"
- select="substring-before($content, ' ')" />
- </xsl:call-template>
- <xsl:text> </xsl:text>
- <xsl:call-template name="arglist">
- <xsl:with-param
- name="arguments"
- select="substring-after($content, ' ')" />
- </xsl:call-template>
- </xsl:when>
- <xsl:when test="$content != ''">
- <xsl:text>@var{</xsl:text>
- <xsl:value-of select="$content" />
- <xsl:text>}</xsl:text>
- </xsl:when>
- <xsl:otherwise />
- </xsl:choose>
- </xsl:template>
-
- <xsl:template match="html:strong">
- <xsl:text>@strong{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:ul">
- <xsl:text>@itemize&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end itemize&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:ol">
- <xsl:text>@enumerate&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@end enumerate&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:li">
- <xsl:text>@item&#xa;</xsl:text>
- <xsl:apply-templates />
- <xsl:text>@c fill&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="html:emph">
- <xsl:text>@emph{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:var">
- <xsl:text>@var{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:gfdl">
- <xsl:text>@include fdl.texi&#xa;&#xa;</xsl:text>
- </xsl:template>
-
- <xsl:template match="info:printindex">
- <xsl:text>@printindex cp&#xa;&#xa;</xsl:text>
- </xsl:template>
-</xsl:stylesheet>
diff --git a/doc/manual.html b/doc/manual.html
deleted file mode 100644
index 20a6bc0..0000000
--- a/doc/manual.html
+++ /dev/null
@@ -1,1337 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:info="http://planete-kraus.eu/ns/info"
- xml:lang="en">
- <head>
- <link rel="stylesheet" type="text/css" href="style.css"/>
- <title>Webid-oidc manual</title>
- <info:subtitle>for version <info:version />,
- <info:updated /></info:subtitle>
- <info:copying>
- <p>
- This is the manual of webid-oidc (version <info:version />,
- <info:updated />), an implementation of the Solid
- authentication protocol for guile, client and server.
- </p>
- <p>Copyright <info:copyright-symbol /> 2020, 2021 Vivien
- Kraus</p>
- <info:quotation>
- <p>
- 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''
- </p>
- </info:quotation>
- </info:copying>
- <info:author>
- <a href="mailto:vivien@planete-kraus.eu">Vivien Kraus</a>
- </info:author>
- <info:dircategory>
- Software libraries
- </info:dircategory>
- <info:direntry>
- <info:direntry-entry name="webid-oidc">
- Decentralized Authentication on the Web.
- </info:direntry-entry>
- </info:direntry>
- </head>
- <body>
- <h1>Decentralized Authentication on the Web</h1>
- <p>
- 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
- <info:var>useful-program</info:var> wants to authenticate
- <info:var>MegaCorp</info:var> users, then
- <info:var>useful-program</info:var> has to register to
- <info:var>MegaCorp</info:var> first, and get approved. This goes
- against the principle of permission-less innovation, which is at
- the heart of the web.
- </p>
- <p>
- 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.
- </p>
- <p>
- 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.
- </p>
- <p>
- 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.
- </p>
- <p>
- 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.
- </p>
- <h1>The Json Web Token</h1>
- <p>
- The Json Web Token, or <info:dfn>JWT</info:dfn>, is a terse
- representation of a pair of JSON objects: the
- <info:dfn>header</info:dfn>, and the
- <info:dfn>payload</info:dfn>. The JWT can be
- <info:dfn>encoded</info:dfn> as a Json Web Signature
- (<info:dfn>JWS</info:dfn>), 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.
- </p>
- <p>
- Decoded JWT are represented as a pair. The car of the pair is
- the header, and the cdr is the payload. Both the header and the
- payload use the JSON representation from srfi-180: objects are
- alists of <strong>symbols</strong> to values, arrays are
- vectors. It is unfortunate that guile-json has a slightly
- different representation, where alist keys are
- <emph>strings</emph>, but we hope that in the future SRFI-180
- will be more closely respected.
- </p>
- <h2>The ID token</h2>
- <p>
- The ID token is a special JWT that the application keeps for
- itself. It is signed by the identity provider, and contains the
- following claims:
- </p>
- <ul>
- <li><emph>webid</emph>, the URI of the user’s webid;</li>
- <li><emph>iss</emph>, the URI of the identity provider (issuer);</li>
- <li><emph>sub</emph>, the username (the webid-oidc issuer puts the webid again here, but it could be any string);</li>
- <li><emph>aud</emph>, the ID of the client application that is intended to receive the ID token;</li>
- <li><emph>nonce</emph>, some random data to change the signature;</li>
- <li><emph>exp</emph>, an UTC time (in seconds) for when the token expires;</li>
- <li><emph>iat</emph>, the time when it was issued.</li>
- </ul>
- <p>
- There are functions to work with ID tokens
- in <emph>(webid-oidc&#160;oidc-id-token)</emph>.
- </p>
- <info:deffn type="function" name="id-token?" arguments="object">
- <p>
- Check that <info:var>object</info:var> is a decoded ID token.
- </p>
- </info:deffn>
- <p>
- The following helper functions convert URIs to the URIs
- from <emph>(web&#160;uri)</emph> and times
- to <emph>(srfi&#160;srfi-19)</emph> dates.
- </p>
- <info:deffn type="function" name="id-token-webid" arguments="token">
- <info:deffnx type="function" name="id-token-iss" arguments="token" />
- <info:deffnx type="function" name="id-token-sub" arguments="token" />
- <info:deffnx type="function" name="id-token-aud" arguments="token" />
- <info:deffnx type="function" name="id-token-nonce" arguments="token" />
- <info:deffnx type="function" name="id-token-exp" arguments="token" />
- <info:deffnx type="function" name="id-token-iat" arguments="token" />
- <p>
- Get the suitable field from the payload
- of <info:var>token</info:var>.
- </p>
- </info:deffn>
- <p>
- ID tokens can be signed and encoded as a string, or decoded.
- </p>
- <info:deffn type="function" name="id-token-decode" arguments="token [#http-get]">
- <p>
- Decode <info:var>token</info:var>, as a string, into a decoded
- token. The signature verification will need to fetch the oidc
- configuration of the claimed issuer, and check the signature
- against the published keys. The <pre>http-get</pre> optional
- keyword argument can set a different implementation
- of <pre>http-get</pre>
- from <emph>(web&#160;client)</emph>. Return <pre>#f</pre> if
- it failed, or the decoded token otherwise.
- </p>
- </info:deffn>
- <info:deffn type="function" name="id-token-encode" arguments="token key">
- <p>
- Encode <info:var>token</info:var> and sign it with the
- issuer’s <info:var>key</info:var>.
- </p>
- </info:deffn>
- <info:deffn type="function" name="issue-id-token" arguments="issuer-key #alg #webid #iss #sub #aud #exp #iat">
- <p>
- Create an ID token, and encode it with
- <info:var>issuer-key</info:var>.
- </p>
- </info:deffn>
- <h2>The access token</h2>
- <p>
- The access token is obtained by the client through a token
- request, and is presented to the server on each authenticated
- request. It is signed by the identity provider, and it contains
- enough information so that the server knows who the user is and
- who the agent is, and most importantly the fingerprint of the
- key that the client should use in a DPoP proof.
- </p>
- <p>
- The API is defined in
- <emph>(webid-oidc&#160;access-token)</emph>.
- </p>
- <info:deffn type="function" name="access-token?" arguments="object">
- <p>
- Check that <info:var>object</info:var> is a decoded access token.
- </p>
- </info:deffn>
- <p>
- There are field getters for the access token:
- </p>
- <info:deffn type="function" name="access-token-webid" arguments="token">
- <info:deffnx type="function" name="access-token-iss" arguments="token" />
- <info:deffnx type="function" name="access-token-aud" arguments="token" />
- <info:deffnx type="function" name="access-token-exp" arguments="token" />
- <info:deffnx type="function" name="access-token-iat" arguments="token" />
- <info:deffnx type="function" name="access-token-cnf/jkt" arguments="token" />
- <info:deffnx type="function" name="access-token-client-id" arguments="token" />
- <p>
- Get the suitable field from the payload
- of <info:var>token</info:var>.
- </p>
- </info:deffn>
- <p>
- Access tokens can be signed and encoded as a string, or decoded.
- </p>
- <info:deffn type="function" name="access-token-decode" arguments="token [#http-get]">
- <p>
- Decode <info:var>token</info:var>, as a string, into a decoded
- token. As with the ID token, the signature verification will
- need to fetch the oidc configuration of the claimed issuer,
- and check the signature against the published keys. The
- <pre>http-get</pre> optional keyword argument can set a
- different implementation of <pre>http-get</pre> from
- <emph>(web&#160;client)</emph>, for instance to re-use the
- what has been obtained by the ID token validation. Return
- <pre>#f</pre> if it failed, or the decoded token otherwise.
- </p>
- </info:deffn>
- <info:deffn type="function" name="access-token-encode" arguments="token key">
- <p>
- Encode <info:var>token</info:var> and sign it with the
- issuer’s <info:var>key</info:var>.
- </p>
- </info:deffn>
- <info:deffn type="function" name="issue-access-token" arguments="issuer-key #alg #webid #iss #exp #iat [#client-key | #cnf/jkt] #client-id ">
- <p>
- Create an access token, and encode it with
- <info:var>issuer-key</info:var>. You can either set the
- <pre>#:cnf/jkt</pre> keyword argument with the fingerprint of
- the client key, or set <pre>#:client-key</pre> directly, in
- which case the fingerprint will be computed for you.
- </p>
- </info:deffn>
- <h2>The DPoP proof</h2>
- <p>
- This is a special JWT, that is signed by a key controlled by the
- application. The access token certifies that the key used to
- sign the proof is approved by the identity provider.
- </p>
- <info:deffn type="function" name="dpop-proof?" arguments="proof">
- <p>
- Check that the <info:var>proof</info:var> is a decoded DPoP
- proof. The validity of the proof is not checked by this
- function.
- </p>
- </info:deffn>
- <info:deffn type="function" name="dpop-proof-alg" arguments="proof">
- <info:deffnx type="function" name="dpop-proof-jwk" arguments="proof" />
- <info:deffnx type="function" name="dpop-proof-jti" arguments="proof" />
- <info:deffnx type="function" name="dpop-proof-htm" arguments="proof" />
- <info:deffnx type="function" name="dpop-proof-htu" arguments="proof" />
- <info:deffnx type="function" name="dpop-proof-iat" arguments="proof" />
- <p>
- Get the corresponding field of the proof.
- </p>
- </info:deffn>
- <info:deffn type="function" name="dpop-proof-decode" arguments="current-time jti-list method uri str cnf/check">
- <p>
- Check and decode a DPoP proof encoded
- as <info:var>str</info:var>.
- </p>
- <p>
- The <info:var>current-time</info:var> is passed as a date,
- time or number (of seconds).
- </p>
- <p>
- In order to prevent replay attacks, each proof has a unique
- random string that is remembered
- in <info:var>jti-list</info:var> until its expiration date is
- reached. See the <pre>make-jti-list</pre> function.
- </p>
- <p>
- The proof is limited to the scope of
- one <info:var>uri</info:var> and
- one <info:var>method</info:var>
- (<pre>'GET</pre>, <pre>'POST</pre> and so on).
- </p>
- <p>
- Finally, the key that is used to sign the proof should be
- confirmed by the identity provider. To this end,
- the <info:var>cnf/check</info:var> function is called with the
- fingerprint of the key. The function should check that the
- fingerprint is OK (return a boolean).
- </p>
- </info:deffn>
- <info:deffn type="function"
- name="make-jti-list"
- arguments="">
- <p>
- This function in <emph>(webid-oidc&#160;jti-list)</emph>
- creates an in-memory, async-safe, thread-safe cache for the
- proof IDs.
- </p>
- </info:deffn>
- <info:deffn type="function" name="dpop-proof-encode" arguments="proof key">
- <p>
- Encode the proof and sign it with <info:var>key</info:var>. To
- generate valid proofs, <info:var>key</info:var> should be the
- private key corresponding to the <pre>jwk</pre> field of the
- proof.
- </p>
- </info:deffn>
- <info:deffn type="function" name="issue-dpop-proof" arguments="client-key #alg #htm #htu #iat">
- <p>
- Create a proof, sign it and encode it with
- <info:var>client-key</info:var>. <info:var>client-key</info:var>
- should contain both the private and public key, because the
- public part is written in the proof and the private part is
- used to sign it.
- </p>
- </info:deffn>
- <h2>Generic JWTs</h2>
- <p>
- You can parse generic JWTs signed with JWS with the following
- functions from <emph>(webid-oidc&#160;jws)</emph>.
- </p>
- <info:deffn type="function" name="jws?" arguments="jwt">
- <p>
- Check that <info:var>jwt</info:var> is a decoded JWT signed
- with JWS.
- </p>
- </info:deffn>
- <info:deffn type="function" name="jws-alg" arguments="jwt">
- <p>
- Get the algorithm used to sign <info:var>jwt</info:var>.
- </p>
- </info:deffn>
- <info:deffn type="function" name="jws-decode" arguments="str lookup-keys">
- <p>
- Check and decode a JWT signed with JWS and encoded
- as <info:var>str</info:var>.
- </p>
- <p>
- Since the decoding and signature verification happen at the
- same time (for user friendliness), the
- <info:var>lookup-keys</info:var> function is used. It is
- passed as arguments the decoded JWT (but the signature is not
- checked yet), and it should return a public key, a public key
- set or a list of public keys. If the key lookup failed, this
- function should raise an exception.
- </p>
- </info:deffn>
- <info:deffn type="function" name="jws-encode" arguments="jwt key">
- <p>
- Encode the JWT and sign it with <info:var>key</info:var>.
- </p>
- </info:deffn>
- <h1>Caching on server side</h1>
- <p>
- 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.
- </p>
- <p>
- 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</emph>. 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.
- </p>
- <p>
- The <emph>(webid-oidc&#160;cache)</emph> module exports two
- functions to deal with the cache.
- </p>
- <info:deffn type="function" name="clean-cache" arguments="[#percents] [#dir]">
- <p>
- Drop <info:var>percents</info:var>% of the cache right now, in
- <info:var>dir</info:var> (defaults to some place within
- <emph>XDG_CACHE_HOME</emph>).
- </p>
- </info:deffn>
- <info:deffn type="function" name="with-cache" arguments="[#current-time] [#http-get] [#dir]">
- <p>
- Return a function acting as <emph>http-get</emph> from
- <emph>(web&#160;client)</emph> (takes an URI as the first
- parameter, and an optional <info:var>#:headers</info:var> set,
- and returns 2 values, the response and its body).
- </p>
- <p>
- The cache will be read and written in <info:var>dir</info:var>
- (defaults to some place within <emph>XDG_CACHE_HOME</emph>),
- and the <info:var>current-time</info:var> number of seconds,
- SRFI-19 time or date, or time-returning thunk will be used to
- check for the validity of responses.
- </p>
- <p>
- The back-end function, <info:var>http-get</info:var>, defaults
- to that of <emph>(web&#160;client)</emph>.
- </p>
- </info:deffn>
- <h1>What if something goes wrong?</h1>
- <p>
- The library will raise an exception whenever something fishy
- occurs. For instance, if a signature is invalid, or the
- expiration date has passed. All exception types are defined in
- <emph>(webid-oidc errors)</emph>.
- </p>
- <info:deffn type="function" name="error->str" arguments="error [#depth]">
- <p>
- Return a string explaining the <info:var>error</info:var>. You
- can limit the <info:var>depth</info:var> of the explanation as
- an integer.
- </p>
- </info:deffn>
- <info:deftp type="exception type" name="&amp;not-base64" arguments="value cause">
- <p>
- This exception is raised when the base64 decoding function
- failed. <info:var>value</info:var> is the incorrect input,
- and <info:var>cause</info:var> is a low-level error.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-json" arguments="value cause">
- <p>
- Cannot decode <info:var>value</info:var> to a JSON object.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-turtle" arguments="value cause">
- <p>
- Cannot decode <info:var>value</info:var> to a RDF graph.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unsupported-crv" arguments="crv">
- <p>
- The identifier <info:var>crv</info:var> does not identify an
- elliptic curve.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-jwk" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a JWK.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-public-jwk" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a public JWK.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-private-jwk" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a private JWK.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-jwks" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a set of public keys.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unsupported-alg" arguments="value">
- <p>
- <info:var>value</info:var> does not identify a valid hash algorithm.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;invalid-signature" arguments="key payload signature">
- <p>
- <info:var>key</info:var> has not signed
- <info:var>payload</info:var> with
- <info:var>signature</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;missing-alist-key" arguments="value key">
- <p>
- <info:var>value</info:var> isn’t an alist, or is missing a
- value with <info:var>key</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-jws-header" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a decoded JWS header.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-jws-payload" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a decoded JWS payload.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-jws" arguments="value cause">
- <p>
- <info:var>value</info:var> does not identify a decoded JWS.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-in-3-parts" arguments="string separator">
- <p>
- <info:var>string</info:var> cannot be split into 3 parts with
- <info:var>separator</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;no-matching-key" arguments="candidates alg payload signature">
- <p>
- No key among <info:var>candidates</info:var> could verify
- <info:var>signature</info:var> signed with
- <info:var>alg</info:var> for <info:var>payload</info:var>,
- because the signature mismatched for all keys.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-decode-jws" arguments="value cause">
- <p>
- The <info:var>value</info:var> string is not an encoding of a valid JWS.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-encode-jws" arguments="jws key cause">
- <p>
- The <info:var>jws</info:var> cannot be signed.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;request-failed-unexpectedly" arguments="response-code response-reason-phrase">
- <p>
- We expected the request to succeed, but the server sent a
- non-OK <info:var>response-code</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unexpected-header-value" arguments="header value">
- <p>
- We did not expect the server to respond with
- <info:var>header</info:var> set to <info:var>value</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unexpected-response" arguments="response cause">
- <p>
- The <info:var>response</info:var> (from
- <emph>(web response)</emph>) is not appropriate.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-oidc-configuration" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an OIDC configuration.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-webid-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the webid field in the JWT
- is missing (if <pre>#f</pre>), or not an acceptable value.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-sub-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the sub field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-iss-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the iss field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-aud-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the aud field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-iat-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the iat field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-exp-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the exp field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-cnf/jkt-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the cnf/jkt field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-client-id-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the client-id field is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-redirect-uris-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the redirect-uris field of a
- client manifest is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-typ-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the typ field in the DPoP proof
- header is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-jwk-field" arguments="value cause">
- <p>
- The <info:var>value</info:var> of the jwk field in the DPoP
- proof header is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-jti-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the jti field in the DPoP
- proof is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-nonce-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the nonce field in the DPoP
- proof is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-htm-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the htm field in the DPoP
- proof is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;incorrect-htu-field" arguments="value">
- <p>
- The <info:var>value</info:var> of the htu field in the DPoP
- proof is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-access-token" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an access token.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-access-token-header" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an access token header.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-access-token-payload" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an access token payload.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-fetch-issuer-configuration" arguments="issuer cause">
- <p>
- It is impossible to fetch the configuration of
- <info:var>issuer</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-fetch-jwks" arguments="issuer uri cause">
- <p>
- It is impossible to fetch the keys of
- <info:var>issuer</info:var> at <info:var>uri</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-decode-access-token" arguments="value cause">
- <p>
- The <info:var>value</info:var> string is not an encoding of a
- valid access token.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-encode-access-token" arguments="access-token key cause">
- <p>
- The <info:var>access-token</info:var> cannot be signed.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-dpop-proof" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not a DPoP proof.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-dpop-proof-header" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not a DPoP proof header.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-dpop-proof-payload" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not a DPoP proof payload.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;dpop-method-mismatch" arguments="signed requested">
- <p>
- The method value <info:var>signed</info:var> in the DPoP proof
- does not match the method that is
- <info:var>requested</info:var> on the server.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;dpop-uri-mismatch" arguments="signed requested">
- <p>
- The URI value <info:var>signed</info:var> in the DPoP proof
- does not match the URI that is <info:var>requested</info:var>
- on the server.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;dpop-signed-in-future" arguments="signed current">
- <p>
- The proof is <info:var>signed</info:var> for a date which is
- too much ahead of the <info:var>current</info:var> time.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;dpop-too-old" arguments="signed current">
- <p>
- The proof was <info:var>signed</info:var> at a past date of
- <info:var>current</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;dpop-unconfirmed-key" arguments="key expected cause">
- <p>
- The confirmation of <info:var>key</info:var> is not what is
- <info:var>expected</info:var>, or (if a function was passed as
- <info:var>cnf/check</info:var>) the <info:var>cause</info:var>
- exception occurred while confirming.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;jti-found" arguments="jti cause">
- <p>
- The <info:var>jti</info:var> of the proof has already been
- issued in a recent past.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-decode-dpop-proof" arguments="value cause">
- <p>
- The <info:var>value</info:var> string is not an encoding of a
- valid DPoP proof.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-encode-dpop-proof" arguments="dpop-proof key cause">
- <p>
- The <info:var>dpop-proof</info:var> cannot be signed.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-fetch-linked-data" arguments="uri cause">
- <p>
- Could not fetch the graph referenced by
- <info:var>uri</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-a-client-manifest" arguments="value cause">
- <p>
- The <info:var>client-manifest</info:var> is incorrect.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unauthorized-redirection-uri" arguments="manifest uri">
- <p>
- The authorization <info:var>uri</info:var> is not advertised
- in <info:var>manifest</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-serve-public-manifest" arguments="">
- <p>
- You cannot serve the public client manifest.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;no-client-manifest-registration" arguments="id">
- <p>
- The <info:var>id</info:var> client manifest does not have a
- registration triple in its document.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;inconsistent-client-manifest-id" arguments="id advertised-id">
- <p>
- The client <info:var>manifest</info:var> is being fetched at
- <info:var>id</info:var>, but it is valid for another client
- <info:var>advertised-id</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-fetch-client-manifest" arguments="id cause">
- <p>
- Could not fetch a client manifest at <info:var>id</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-authorization-code" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an authorization code.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-authorization-code-header" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an authorization code header.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-authorization-code-payload" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an authorization code payload.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;authorization-code-expired" arguments="exp current-time">
- <p>
- The authorization code has expired at
- <info:var>exp</info:var>, it is now
- <info:var>current-time</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-decode-authorization-code" arguments="value cause">
- <p>
- The <info:var>value</info:var> string is not an encoding of a
- valid authorization code.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-encode-authorization-code" arguments="authorization-code key cause">
- <p>
- The <info:var>authorization-code</info:var> cannot be signed.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;invalid-refresh-token" arguments="refresh-token">
- <p>
- The <info:var>refresh-token</info:var> is unknown to the
- identity provider.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;invalid-key-for-refresh-token" arguments="key jkt">
- <p>
- The refresh token was issued for <info:var>jkt</info:var>, but
- it is used with <info:var>key</info:var>.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-id-token" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an ID token.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-id-token-header" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an ID token header.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;not-an-id-token-payload" arguments="value cause">
- <p>
- The <info:var>value</info:var> is not an ID token payload.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-decode-id-token" arguments="value cause">
- <p>
- The <info:var>value</info:var> string is not an encoding of a
- valid ID token.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;cannot-encode-id-token" arguments="id-token key cause">
- <p>
- The <info:var>id-token</info:var> cannot be signed.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unknown-client-locale" arguments="web-locale c-locale">
- <p>
- The <info:var>web-locale</info:var> of the client, translated
- to C as <info:var>c-locale</info:var>, cannot be set. This
- exception is always continuable; if the handler returns, then
- the page will be served in the english locale.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unsupported-grant-type" arguments="value">
- <p>
- The token request failed to indicate a
- <info:var>value</info:var> for the grant type, or indicated an
- unsupported grant type.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;no-authorization-code" arguments="">
- <p>
- The token request forgot to put an authorization code.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;no-refresh-token" arguments="">
- <p>
- The token request forgot to put a refresh token with the
- request.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;unconfirmed-provider" arguments="subject provider">
- <p>
- <info:var>provider</info:var> is not confirmed by
- <info:var>subject</info:var> as an identity provider.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;no-provider-candidates" arguments="webid causes">
- <p>
- The <info:var>webid</info:var> cannot be certified by any
- identity providers. The <info:var>causes</info:var> alist
- indicates an error for each candidates.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;neither-identity-provider-nor-webid" arguments="uri why-not-identity-provider why-not-webid">
- <p>
- The <info:var>uri</info:var> you passed to get an
- authorization code is neither an identity provider (because
- <info:var>why-not-identity-provider</info:var>) nor a webid
- (because <info:var>why-not-webid</info:var>).
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;token-request-failed" arguments="cause">
- <p>
- The token request failed on the server.
- </p>
- </info:deftp>
- <info:deftp type="exception type" name="&amp;profile-not-found" arguments="webid iss dir">
- <p>
- The <info:var>webid</info:var>, as certified by
- <info:var>iss</info:var>, cannot be refreshed because we don’t
- have a refresh token stored in <info:var>dir</info:var>.
- </p>
- </info:deftp>
-
- <h1>Running an Identity Provider</h1>
- <p>
- This project is packaged with a barebones identity provider. It
- has an authorization endpoint and a token endpoint (and it
- serves its public keys), but it is only intended for one
- specific person.
- </p>
- <p>
- You can start it by invoking the <pre>webid-oidc-issuer</pre>
- program, with the following options:
- </p>
- <ul>
- <li>
- <pre>-h</pre>, or <pre>--help</pre> prints a summary of
- options and exit.
- </li>
- <li>
- <pre>-v</pre>, or <pre>--version</pre> prints the version of
- the program and exits.
- </li>
- <li>
- <pre>-i <info:var>URI</info:var></pre>, or
- <pre>--issuer=<info:var>URI</info:var></pre> sets the global
- server name of the identity provider. It should have an empty
- path.
- </li>
- <li>
- <pre>-k <info:var>FILE.jwk</info:var></pre>, or
- <pre>--key-file=<info:var>FILE.jwk</info:var></pre> sets the
- file name where to read or generate a key for the identity
- provider. This file should be JSON, containing the
- representation of a JWK key pair.
- </li>
- <li>
- <pre>-s <info:var>WEBID</info:var></pre>, or
- <pre>--subject=<info:var>WEBID</info:var></pre> sets the webid
- of the only user of the identity provider. This is an URI,
- pointing to a RDF node corresponding to the user’s profile.
- </li>
- <li>
- <pre>-w <info:var>PASSWORD</info:var></pre>, or
- <pre>--password=<info:var>PASSWORD</info:var></pre>, sets the
- password that the user must enter to authorize an
- application.
- </li>
- <li>
- <pre>-j <info:var>URI</info:var></pre>, or
- <pre>--jwks-uri=<info:var>URI</info:var></pre> tells the
- server that requests to <info:var>URI</info:var> should be
- responded with the public key used to sign the tokens.
- </li>
- <li>
- <pre>-a <info:var>URI</info:var></pre>, or
- <pre>--authorization-endpoint-uri=<info:var>URI</info:var></pre>
- tells the server that requests to <info:var>URI</info:var>
- should be treated as authorization requests.
- </li>
- <li>
- <pre>-t <info:var>URI</info:var></pre>, or
- <pre>--token-endpoint-uri=<info:var>URI</info:var></pre> tells
- the server that requests to <info:var>URI</info:var> should be
- treated as token negociation requests.
- </li>
- <li>
- <pre>-p <info:var>PORT</info:var></pre>, or
- <pre>--port=<info:var>PORT</info:var></pre>, change the port
- number used by the server. By default, it is set to 8080.
- </li>
- <li>
- <pre>-l <info:var>FILE.log</info:var></pre>, or
- <pre>--log-file=<info:var>FILE.log</info:var></pre> let the
- server dump all its output to
- <info:var>FILE.log</info:var>. Since I don’t know how to deal
- with syslog, this is the only way to keep logs with a shepherd
- service.
- </li>
- <li>
- <pre>-e <info:var>FILE.err</info:var></pre>, or
- <pre>--error-file=<info:var>FILE.err</info:var></pre> let the
- server dump all its errors to <info:var>FILE.err</info:var>.
- </li>
- </ul>
- <p>
- The program is sensitive to the environment variables. The most
- important one is <emph>LANG</emph>, which influences how the
- program is internationalized to the server administrator (the
- pages served to the user use the user agent’s locale). This
- changes the long form of the options, and the language in the
- log files.
- </p>
- <p>
- The <emph>XDG_DATA_HOME</emph> should point to some place where
- the program will store refresh tokens, under the
- <pre>webid-oidc</pre> directory. For a system service, you might
- want to define that environment to <pre>/var/lib</pre>, for
- instance.
- </p>
- <p>
- The <emph>XDG_CACHE_HOME</emph> should point to a directory
- where to store the seed of the random number generator (under a
- <pre>webid-oidc</pre> directory, again). Changing the seed only
- happens when a program starts to require the random number
- generator. You can safely delete this directory, but you need to
- restart the program to actually change the seed.
- </p>
- <h1>Running a Resource Server</h1>
- <p>
- A Solid server is the server that manages your data. It needs to
- check that the proofs of possession are correct, and the
- possessed key is signed by the identity provider.
- </p>
- <h2>Running webid-oidc-reverse-proxy</h2>
- <p>
- The distribution comes with a reverse proxy, aptly named
- <pre>webid-oidc-reverse-proxy</pre>, to listen to an interface,
- take requests, authenticate them, and pass them to a backend
- with an additional header containing the webid of the agent, if
- authenticated.
- </p>
- <p>The reverse proxy is invoked with the following arguments:</p>
- <ul>
- <li>
- <pre>-p</pre> <info:var>PORT</info:var>,
- <pre>--port=</pre><info:var>PORT</info:var>: the port on which
- the reverse proxy listens;
- </li>
- <li>
- <pre>-i</pre> <info:var>INBOUND</info:var>,
- <pre>--inbound-uri=</pre><info:var>INBOUND</info:var>: the
- public name of the server;
- </li>
- <li>
- <pre>-o</pre> <info:var>OUTBOUND</info:var>,
- <pre>--outbound-uri=</pre><info:var>OUTBOUND</info:var>: the
- address of the backend;
- </li>
- <li>
- <pre>-H</pre> <info:var>HEADER</info:var>,
- <pre>--header=</pre><info:var>HEADER</info:var>: replace the
- name of the header that will contain the webid of the
- user. Defaults to <pre>XXX-Agent</pre>. Please note that this
- value should be ASCII, otherwise it’s not guaranteed that the
- reverse proxy will drop other capitalizations of the header in
- malicious requests.
- </li>
- <li>
- <pre>-l <info:var>FILE.log</info:var></pre>, or
- <pre>--log-file=<info:var>FILE.log</info:var></pre> let the
- server dump all its output to
- <info:var>FILE.log</info:var>. See the identity provider
- comment.
- </li>
- <li>
- <pre>-e <info:var>FILE.err</info:var></pre>, or
- <pre>--error-file=<info:var>FILE.err</info:var></pre> let the
- server dump all its errors to <info:var>FILE.err</info:var>.
- </li>
- </ul>
- <p>
- You can localize the interface by setting the
- <info:var>LANG</info:var> environment variable.
- </p>
- <h2>The authenticator</h2>
- <p>
- In <emph>(webid-oidc&#160;jws)</emph>, the following function
- gives a simple API for a web server:
- </p>
- <info:deffn type="function"
- name="make-authenticator"
- arguments="jti-list [#server-uri] [#current-time] [#http-get]">
- <p>
- Create an authenticator, i.e. a function that takes a request
- and request body and returns the webid of the authenticated
- user, or <pre>#f</pre> if it is not authenticated.
- </p>
- <p>
- To prevent replay attacks, each request is signed by the
- client with a different unique padding value. If such a value
- has already been seen, then the request must fail.
- </p>
- <p>
- The authenticator expects the client to demonstrate the
- possession of a key that the identity provider knows. So the
- client creates a DPoP proof, targetted to a specific URI. In
- order to check that the URI is correct, the authenticator
- needs the public URI of the service.
- </p>
- <p>
- The JTIs are checked within a small time frame. By default,
- the system time will be used. Otherwise, you can customize the
- <pre>current-time</pre> optional keyword argument, to pass a
- thunk returning a time from <emph>(srfi srfi-19)</emph>.
- </p>
- <p>
- You may want to customize the <info:var>http-get</info:var>
- optional keyword argument to pass a function to replace
- <pre>http-get</pre> from <emph>(http client)</emph>. This
- function takes an URI and optional <pre>#:headers</pre>
- arguments, makes the request, and return two values: the
- response, and the response body.
- </p>
- <p>
- This function, in
- <emph>(webid-oidc&#160;resource-server)</emph>, returns a web
- request handler, taking the request and request body, and
- returning the subject of the access token. If an error
- happens, it is thrown; the function always returns a valid
- URI.
- </p>
- </info:deffn>
-
- <h1>Running a client</h1>
- <p>
- To run a client, you need to proceed in two steps. First,
- acquire an OIDC ID token and an access token from the identity
- provider, and then present the access token and a proof of
- possession of the linked key in each request, in a DPoP HTTP
- header.
- </p>
- <p>
- The first operation is performed by the
- <emph>(webid-oidc&#160;client)</emph> module.
- </p>
- <info:deffn type="function" name="authorize" arguments="host/webid #client-id #redirect-uri [#state] [#http-get]">
- <p>
- The user enters a valid webid or a host name, and then this
- function will query it (with the <info:var>http-get</info:var>
- parameter, by default the web client from
- <emph>(web&#160;client)</emph>) to determine the authorization
- endpoint. The function will return an alist of authorization
- URIs, indexed by approved identity provider URIs, that the
- user should browse with a traditional web browser.
- </p>
- <p>
- Each application should have its own webid, or in that case
- <info:var>client-id</info:var>, that can be dereferenced by
- the identity provider.
- </p>
- <p>
- Once the user has given authorization, the user’s agent will
- be redirected to <info:var>redirect-uri</info:var>, with the
- authorization code as a GET parameter. It is possible to pass
- a <info:var>state</info:var>, but this is optional.
- </p>
- </info:deffn>
- <p>
- Once the client gets the authorization code, it is necessary to
- create an access token and ID token.
- </p>
- <info:deffn type="function" name="token" arguments="host client-key [#authorization-code] [#refresh-token] [#http-get] [#http-post] [#current-time]">
- <p>
- Trade an <info:var>authorization-code</info:var>, or a
- <info:var>refresh-token</info:var>, for an ID token and an
- access token bound to the <info:var>client-key</info:var>
- issued by <info:var>host</info:var>, the identity provider.
- </p>
- <p>
- You can override the HTTP client used
- (<info:var>http-get</info:var> and
- <info:var>http-post</info:var>), and how to compute the time
- (<info:var>current-time</info:var>).
- </p>
- </info:deffn>
- <p>
- In an application, you would have a list of profiles in
- XDG_DATA_HOME, consisting of triples (webid, issuer, refresh
- token).
- </p>
- <info:deffn type="function" name="list-profiles" arguments="[#dir]">
- <p>
- Read the list of available profiles. Returns a list of
- triples, webid, issuer, reresh token.
- </p>
- <p>
- By default, this function will look for the profiles file in
- XDG_DATA_HOME. You can bypass it by providing the
- <info:var>#dir</info:var> optional keyword argument.
- </p>
- </info:deffn>
- <info:deffn type="function" name="setup" arguments="get-host/webid choose-provider browse-authorization-uri #client-id #redirect-uri [#dir] [#http-get] [#http-post] [#current-time]">
- <p>
- Negociate a refresh token, and save it. The function returns 3
- values: the decoded ID token pyload, the encoded access token
- and the key pair.
- </p>
- <p>
- The <info:var>get-host/webid</info:var> thunk should ask the
- user’s webid or identity provider, and return
- it. <info:var>choose-provider</info:var> is called with a list
- of possible identity providers as host names (strings), and
- the user should choose one. The chosen one is
- returned. Finally,
- <info:var>browse-authorization-uri</info:var> should ask or
- let the user browse an URI as its argument, and return the
- authorization code taken from the redirect URI.
- </p>
- <p>
- The refresh token is saved to disk, as a profile, in
- XDG_DATA_HOME. Pass the optional <info:var>#dir</info:var>
- keyword argument to override the location.
- </p>
- <p>
- You need to set <info:var>client-id</info:var> to the public
- webid of the app, and <info:var>redirect-uri</info:var> to one
- of the approved redirection URIs for the application ID.
- </p>
- </info:deffn>
- <info:deffn type="function" name="login" arguments="webid issuer refresh-token key [#dir] [#http-get] [#http-post] [#current-time]">
- <p>
- If you have already a known profile, you can use it to
- automatically log in. This function might update the refresh
- token if it changed, so you can again set
- <info:var>#dir</info:var>. Please note that the
- <info:var>refresh-token</info:var> is bound to the client
- <info:var>key</info:var> on server side, so you must always
- use the same <info:var>key</info:var>.
- </p>
- </info:deffn>
- <info:deffn type="function" name="refresh" arguments="id-token key [#dir] [#http-get] [#http-post] [#current-time]">
- <p>
- If you have an ID token bound to a known profile, this helper
- function will look up the associated refresh token and log in.
- </p>
- </info:deffn>
- <info:deffn type="function" name="make-client" arguments="id-token access-token key [#dir] [#http-get] [#http-post] [#http-request] [#current-time]">
- <p>
- Return a replacement of <pre>http-request</pre> from
- <emph>(web&#160;client)</emph>, that automatically signs
- requests and refresh the tokens when needed.
- </p>
- <p>
- <info:var>#http-get</info:var> and
- <info:var>#http-post</info:var> are only used to refresh the
- tokens, while <info:var>#http-request</info:var> is used as a
- back-end for the requests.
- </p>
- <p>
- <info:var>#current-time</info:var> is set to a thunk that
- returns the time. It is used to issue DPoP proofs.
- </p>
- </info:deffn>
- <p>
- An example application is provided as the
- <pre>webid-oidc-example-app</pre> program. It demonstrates how
- authentication is done. It should help you understand how
- webid-oidc works.
- </p>
- <p>
- The identity provider needs to call the application on the
- web. So, your client should have a public endpoint on the web.
- </p>
- <info:deffn type="function" name="serve-application" arguments="id redirect-uri [#client-name] [#client-uri]">
- <p>
- Return a handler for web requests to serve the application
- manifest and the redirection to transmit the authorization
- code. You should set the <info:var>client-name</info:var> to
- your application name and <info:var>client-uri</info:var> to
- point to where to a presentation of your application.
- </p>
- </info:deffn>
- <p>
- The <pre>webid-oidc-client-service</pre> program can run a
- server to serve these resources. It is invoked with the
- following options:
- </p>
- <ul>
- <li>
- <pre>-h</pre>, or <pre>--help</pre> prints a summary of the
- options and exit.
- </li>
- <li>
- <pre>-v</pre>, or <pre>--version</pre> prints the version of
- the program and exits.
- </li>
- <li>
- <pre>-i <info:var>URI</info:var></pre>, or
- <pre>--client-id=<info:var>URI</info:var></pre> sets the
- global identitifier of the application, which is dereferenced
- to a semantic resource.
- </li>
- <li>
- <pre>-r <info:var>URI</info:var></pre>, or
- <pre>--redirect-uri=<info:var>URI</info:var></pre> sets the
- redirection URI.
- </li>
- <li>
- <pre>-n <info:var>NAME</info:var></pre>, or
- <pre>--client-name=<info:var>NAME</info:var></pre> sets the
- name of your application, so that it is shown when the user
- gets an authorization. The webid-oidc issuer program that
- comes with this package does not display it, because it could
- be dishonest, but other implementations might.
- </li>
- <li>
- <pre>-u <info:var>URI</info:var></pre>, or
- <pre>--client-uri=<info:var>URI</info:var></pre>, sets an URI
- for the identity provider to learn more about your app.
- </li>
- <li>
- <pre>-p <info:var>PORT</info:var></pre>, or
- <pre>--port=<info:var>PORT</info:var></pre>, change the port
- number used by the server. By default, it is set to 8080.
- </li>
- <li>
- <pre>-l <info:var>FILE.log</info:var></pre>, or
- <pre>--log-file=<info:var>FILE.log</info:var></pre> let the
- server dump all its output to <info:var>FILE.log</info:var>.
- </li>
- <li>
- <pre>-e <info:var>FILE.err</info:var></pre>, or
- <pre>--error-file=<info:var>FILE.err</info:var></pre> let the
- server dump all its errors to <info:var>FILE.err</info:var>.
- </li>
- </ul>
- <p>
- The program is sensitive to the environment variable
- <emph>LANG</emph>, which influences how the program is
- internationalized to the server administrator. This changes the
- long form of the options, and the language in the log files.
- </p>
-
- <h1 type="appendix">GNU Free Documentation License</h1>
- <info:gfdl />
-
- <h1 type="unnumbered">Index</h1>
- <info:printindex />
- </body>
-</html>
-
-<!-- Local Variables: -->
-<!-- mode: nxml -->
-<!-- End: -->
diff --git a/doc/webid-oidc.texi b/doc/webid-oidc.texi
new file mode 100644
index 0000000..cf49804
--- /dev/null
+++ b/doc/webid-oidc.texi
@@ -0,0 +1,821 @@
+\input texinfo @c -*-texinfo-*-
+@comment $Id@w{$}
+@documentlanguage en
+@comment %**start of header
+@include version.texi
+@settitle Webid-oidc manual
+@syncodeindex pg cp
+@syncodeindex fn cp
+@syncodeindex vr cp
+@syncodeindex tp cp
+@comment %**end of header
+
+@copying
+This is the manual of webid-oidc (version @value{VERSION}, @value{UPDATED}), an implementation of the Solid authentication protocol for guile, client and server.@c fill
+
+Copyright @copyright{} 2020, 2021 Vivien Kraus@c fill
+@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''@c fill
+@end quotation
+@end copying
+
+@dircategory Software libraries
+
+@direntry
+* webid-oidc: (webid-oidc)Decentralized Authentication on the Web.
+@end direntry
+
+@titlepage
+@title Webid-oidc manual
+@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 Webid-oidc
+@end ifnottex
+
+@menu
+* Decentralized Authentication on the Web::
+* The Json Web Token::
+* Caching on server side::
+* Running an Identity Provider::
+* Running a Resource Server::
+* Running a client::
+* 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.@c fill
+
+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.@c fill
+
+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.@c fill
+
+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.@c fill
+
+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.@c fill
+
+@node The Json Web Token
+@chapter The Json Web Token
+
+@menu
+* The ID token::
+* The access token::
+* The DPoP proof::
+* Generic JWTs::
+@end menu
+
+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.@c fill
+
+Decoded JWT are represented as a pair. The car of the pair is the header, and the cdr is the payload. Both the header and the payload use the JSON representation from srfi-180: objects are alists of @strong{symbols} to values, arrays are vectors. It is unfortunate that guile-json has a slightly different representation, where alist keys are @emph{strings}, but we hope that in the future SRFI-180 will be more closely respected.@c fill
+
+@node The ID token
+@section The ID token
+
+The ID token is a special JWT that the application keeps for itself. It is signed by the identity provider, and contains the following claims:@c fill
+
+@itemize
+@item
+@emph{webid}, the URI of the user’s webid;@c fill
+@item
+@emph{iss}, the URI of the identity provider (issuer);@c fill
+@item
+@emph{sub}, the username (the webid-oidc issuer puts the webid again here, but it could be any string);@c fill
+@item
+@emph{aud}, the ID of the client application that is intended to receive the ID token;@c fill
+@item
+@emph{nonce}, some random data to change the signature;@c fill
+@item
+@emph{exp}, an UTC time (in seconds) for when the token expires;@c fill
+@item
+@emph{iat}, the time when it was issued.@c fill
+@end itemize
+There are functions to work with ID tokens in @code{(webid-oidc oidc-id-token)}.@c fill
+
+@deffn function id-token? @var{object}
+Check that @var{object} is a decoded ID token.@c fill
+@end deffn
+
+The following helper functions convert URIs to the URIs from @code{(web uri)} and times to @emph{(srfi srfi-19)} dates.@c fill
+
+@deffn function id-token-webid @var{token}
+@deffnx function id-token-iss @var{token}
+@deffnx function id-token-sub @var{token}
+@deffnx function id-token-aud @var{token}
+@deffnx function id-token-nonce @var{token}
+@deffnx function id-token-exp @var{token}
+@deffnx function id-token-iat @var{token}
+Get the suitable field from the payload of @var{token}.@c fill
+@end deffn
+
+ID tokens can be signed and encoded as a string, or decoded.@c fill
+
+@deffn function id-token-decode @var{token} @var{[#http-get]}
+Decode @var{token}, as a string, into a decoded token. The signature verification will need to fetch the oidc configuration of the claimed issuer, and check the signature against the published keys. The @code{http-get} optional keyword argument can set a different implementation of @code{http-get} from @code{(web client)}. Return @code{#f} if it failed, or the decoded token otherwise.@c fill
+@end deffn
+
+@deffn function id-token-encode @var{token} @var{key}
+Encode @var{token} and sign it with the issuer’s @var{key}.@c fill
+@end deffn
+
+@deffn function issue-id-token @var{issuer-key} @var{#alg} @var{#webid} @var{#iss} @var{#sub} @var{#aud} @var{#exp} @var{#iat}
+Create an ID token, and encode it with @var{issuer-key}.@c fill
+@end deffn
+
+@node The access token
+@section The access token
+
+The access token is obtained by the client through a token request, and is presented to the server on each authenticated request. It is signed by the identity provider, and it contains enough information so that the server knows who the user is and who the agent is, and most importantly the fingerprint of the key that the client should use in a DPoP proof.@c fill
+
+The API is defined in @code{(webid-oidc access-token)}.@c fill
+
+@deffn function access-token? @var{object}
+Check that @var{object} is a decoded access token.@c fill
+@end deffn
+
+There are field getters for the access token:@c fill
+
+@deffn function access-token-webid @var{token}
+@deffnx function access-token-iss @var{token}
+@deffnx function access-token-aud @var{token}
+@deffnx function access-token-exp @var{token}
+@deffnx function access-token-iat @var{token}
+@deffnx function access-token-cnf/jkt @var{token}
+@deffnx function access-token-client-id @var{token}
+Get the suitable field from the payload of @var{token}.@c fill
+@end deffn
+
+Access tokens can be signed and encoded as a string, or decoded.@c fill
+
+@deffn function access-token-decode @var{token} @var{[#http-get]}
+Decode @var{token}, as a string, into a decoded token. As with the ID token, the signature verification will need to fetch the oidc configuration of the claimed issuer, and check the signature against the published keys. The @code{http-get} optional keyword argument can set a different implementation of @code{http-get} from @code{(web client)}, for instance to re-use the what has been obtained by the ID token validation. Return @code{#f} if it failed, or the decoded token otherwise.@c fill
+@end deffn
+
+@deffn function access-token-encode @var{token} @var{key}
+Encode @var{token} and sign it with the issuer’s @var{key}.@c fill
+@end deffn
+
+@deffn function issue-access-token @var{issuer-key} @var{#alg} @var{#webid} @var{#iss} @var{#exp} @var{#iat} @var{[#client-key} @var{|} @var{#cnf/jkt]} @var{#client-id}
+Create an access token, and encode it with @var{issuer-key}. You can either set the @code{#:cnf/jkt} keyword argument with the fingerprint of the client key, or set @code{#:client-key} directly, in which case the fingerprint will be computed for you.@c fill
+@end deffn
+
+@node The DPoP proof
+@section The DPoP proof
+
+This is a special JWT, that is signed by a key controlled by the application. The access token certifies that the key used to sign the proof is approved by the identity provider.@c fill
+
+@deffn function dpop-proof? @var{proof}
+Check that the @var{proof} is a decoded DPoP proof. The validity of the proof is not checked by this function.@c fill
+@end deffn
+
+@deffn function dpop-proof-alg @var{proof}
+@deffnx function dpop-proof-jwk @var{proof}
+@deffnx function dpop-proof-jti @var{proof}
+@deffnx function dpop-proof-htm @var{proof}
+@deffnx function dpop-proof-htu @var{proof}
+@deffnx function dpop-proof-iat @var{proof}
+Get the corresponding field of the proof.@c fill
+@end deffn
+
+@deffn function dpop-proof-decode @var{current-time} @var{jti-list} @var{method} @var{uri} @var{str} @var{cnf/check}
+Check and decode a DPoP proof encoded as @var{str}.@c fill
+
+The @var{current-time} is passed as a date, time or number (of seconds).@c fill
+
+In order to prevent replay attacks, each proof has a unique random string that is remembered in @var{jti-list} until its expiration date is reached. See the @code{make-jti-list} function.@c fill
+
+The proof is limited to the scope of one @var{uri} and one @var{method} (@code{'GET}, @code{'POST} and so on).@c fill
+
+Finally, the key that is used to sign the proof should be confirmed by the identity provider. To this end, the @var{cnf/check} function is called with the fingerprint of the key. The function should check that the fingerprint is OK (return a boolean).@c fill
+@end deffn
+
+@deffn function make-jti-list
+This function in @code{(webid-oidc jti-list)} creates an in-memory, async-safe, thread-safe cache for the proof IDs.@c fill
+@end deffn
+
+@deffn function dpop-proof-encode @var{proof} @var{key}
+Encode the proof and sign it with @var{key}. To generate valid proofs, @var{key} should be the private key corresponding to the @code{jwk} field of the proof.@c fill
+@end deffn
+
+@deffn function issue-dpop-proof @var{client-key} @var{#alg} @var{#htm} @var{#htu} @var{#iat}
+Create a proof, sign it and encode it with @var{client-key}. @var{client-key} should contain both the private and public key, because the public part is written in the proof and the private part is used to sign it.@c fill
+@end deffn
+
+@node Generic JWTs
+@section Generic JWTs
+
+You can parse generic JWTs signed with JWS with the following functions from @code{(webid-oidc jws)}.@c fill
+
+@deffn function jws? @var{jwt}
+Check that @var{jwt} is a decoded JWT signed with JWS.@c fill
+@end deffn
+
+@deffn function jws-alg @var{jwt}
+Get the algorithm used to sign @var{jwt}.@c fill
+@end deffn
+
+@deffn function jws-decode @var{str} @var{lookup-keys}
+Check and decode a JWT signed with JWS and encoded as @var{str}.@c fill
+
+Since the decoding and signature verification happen at the same time (for user friendliness), the @var{lookup-keys} function is used. It is passed as arguments the decoded JWT (but the signature is not checked yet), and it should return a public key, a public key set or a list of public keys. If the key lookup failed, this function should raise an exception.@c fill
+@end deffn
+
+@deffn function jws-encode @var{jwt} @var{key}
+Encode the JWT and sign it with @var{key}.@c fill
+@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.@c fill
+
+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.@c fill
+
+The @code{(webid-oidc cache)} module exports two functions to deal with the cache.@c fill
+
+@deffn function clean-cache @var{[#percents]} @var{[#dir]}
+Drop @var{percents}% of the cache right now, in @var{dir} (defaults to some place within @emph{XDG_CACHE_HOME}).@c fill
+@end deffn
+
+@deffn function with-cache @var{[#current-time]} @var{[#http-get]} @var{[#dir]}
+Return a function acting as @emph{http-get} from @code{(web client)} (takes an URI as the first parameter, and an optional @var{#:headers} set, and returns 2 values, the response and its body).@c fill
+
+The cache will be read and written in @var{dir} (defaults to some place within @emph{XDG_CACHE_HOME}), and the @var{current-time} number of seconds, SRFI-19 time or date, or time-returning thunk will be used to check for the validity of responses.@c fill
+
+The back-end function, @var{http-get}, defaults to that of @code{(web client)}.@c fill
+@end deffn
+
+@node Running an Identity Provider
+@chapter Running an Identity Provider
+
+This project is packaged with a barebones identity provider. It has an authorization endpoint and a token endpoint (and it serves its public keys), but it is only intended for one specific person.@c fill
+
+You can start it by invoking the @code{webid-oidc-issuer} program, with the following options:@c fill
+
+@itemize
+@item
+@code{-h}, or @code{--help} prints a summary of options and exit.@c fill
+@item
+@code{-v}, or @code{--version} prints the version of the program and exits.@c fill
+@item
+@code{-i @var{URI}}, or @code{--issuer=@var{URI}} sets the global server name of the identity provider. It should have an empty path.@c fill
+@item
+@code{-k @var{FILE.jwk}}, or @code{--key-file=@var{FILE.jwk}} sets the file name where to read or generate a key for the identity provider. This file should be JSON, containing the representation of a JWK key pair.@c fill
+@item
+@code{-s @var{WEBID}}, or @code{--subject=@var{WEBID}} sets the webid of the only user of the identity provider. This is an URI, pointing to a RDF node corresponding to the user’s profile.@c fill
+@item
+@code{-w @var{PASSWORD}}, or @code{--password=@var{PASSWORD}}, sets the password that the user must enter to authorize an application.@c fill
+@item
+@code{-j @var{URI}}, or @code{--jwks-uri=@var{URI}} tells the server that requests to @var{URI} should be responded with the public key used to sign the tokens.@c fill
+@item
+@code{-a @var{URI}}, or @code{--authorization-endpoint-uri=@var{URI}} tells the server that requests to @var{URI} should be treated as authorization requests.@c fill
+@item
+@code{-t @var{URI}}, or @code{--token-endpoint-uri=@var{URI}} tells the server that requests to @var{URI} should be treated as token negociation requests.@c fill
+@item
+@code{-p @var{PORT}}, or @code{--port=@var{PORT}}, change the port number used by the server. By default, it is set to 8080.@c fill
+@item
+@code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} let the server dump all its output to @var{FILE.log}. Since I don’t know how to deal with syslog, this is the only way to keep logs with a shepherd service.@c fill
+@item
+@code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} let the server dump all its errors to @var{FILE.err}.@c fill
+@end itemize
+The program is sensitive to the environment variables. The most important one is @emph{LANG}, which influences how the program is internationalized to the server administrator (the pages served to the user use the user agent’s locale). This changes the long form of the options, and the language in the log files.@c fill
+
+The @emph{XDG_DATA_HOME} should point to some place where the program will store refresh tokens, under the @code{webid-oidc} directory. For a system service, you might want to define that environment to @code{/var/lib}, for instance.@c fill
+
+The @emph{XDG_CACHE_HOME} should point to a directory where to store the seed of the random number generator (under a @code{webid-oidc} directory, again). Changing the seed only happens when a program starts to require the random number generator. You can safely delete this directory, but you need to restart the program to actually change the seed.@c fill
+
+@node Running a Resource Server
+@chapter Running a Resource Server
+
+@menu
+* Running webid-oidc-reverse-proxy::
+* The authenticator::
+@end menu
+
+A Solid server is the server that manages your data. It needs to check that the proofs of possession are correct, and the possessed key is signed by the identity provider.@c fill
+
+@node Running webid-oidc-reverse-proxy
+@section Running webid-oidc-reverse-proxy
+
+The distribution comes with a reverse proxy, aptly named @code{webid-oidc-reverse-proxy}, to listen to an interface, take requests, authenticate them, and pass them to a backend with an additional header containing the webid of the agent, if authenticated.@c fill
+
+The reverse proxy is invoked with the following arguments:@c fill
+
+@itemize
+@item
+@code{-p}@var{PORT}, @code{--port=}@var{PORT}: the port on which the reverse proxy listens;@c fill
+@item
+@code{-i}@var{INBOUND}, @code{--inbound-uri=}@var{INBOUND}: the public name of the server;@c fill
+@item
+@code{-o}@var{OUTBOUND}, @code{--outbound-uri=}@var{OUTBOUND}: the address of the backend;@c fill
+@item
+@code{-H}@var{HEADER}, @code{--header=}@var{HEADER}: replace the name of the header that will contain the webid of the user. Defaults to @code{XXX-Agent}. Please note that this value should be ASCII, otherwise it’s not guaranteed that the reverse proxy will drop other capitalizations of the header in malicious requests.@c fill
+@item
+@code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} let the server dump all its output to @var{FILE.log}. See the identity provider comment.@c fill
+@item
+@code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} let the server dump all its errors to @var{FILE.err}.@c fill
+@end itemize
+You can localize the interface by setting the @var{LANG} environment variable.@c fill
+
+@node The authenticator
+@section The authenticator
+
+In @code{(webid-oidc jws)}, the following function gives a simple API for a web server:@c fill
+
+@deffn function make-authenticator @var{jti-list} @var{[#server-uri]} @var{[#current-time]} @var{[#http-get]}
+Create an authenticator, i.e. a function that takes a request and request body and returns the webid of the authenticated user, or @code{#f} if it is not authenticated.@c fill
+
+To prevent replay attacks, each request is signed by the client with a different unique padding value. If such a value has already been seen, then the request must fail.@c fill
+
+The authenticator expects the client to demonstrate the possession of a key that the identity provider knows. So the client creates a DPoP proof, targetted to a specific URI. In order to check that the URI is correct, the authenticator needs the public URI of the service.@c fill
+
+The JTIs are checked within a small time frame. By default, the system time will be used. Otherwise, you can customize the @code{current-time} optional keyword argument, to pass a thunk returning a time from @emph{(srfi srfi-19)}.@c fill
+
+You may want to customize the @var{http-get} optional keyword argument to pass a function to replace @code{http-get} from @emph{(http client)}. This function takes an URI and optional @code{#:headers} arguments, makes the request, and return two values: the response, and the response body.@c fill
+
+This function, in @code{(webid-oidc resource-server)}, returns a web request handler, taking the request and request body, and returning the subject of the access token. If an error happens, it is thrown; the function always returns a valid URI.@c fill
+@end deffn
+
+@node Running a client
+@chapter Running a client
+
+To run a client, you need to proceed in two steps. First, acquire an OIDC ID token and an access token from the identity provider, and then present the access token and a proof of possession of the linked key in each request, in a DPoP HTTP header.@c fill
+
+The first operation is performed by the @code{(webid-oidc client)} module.@c fill
+
+@deffn function authorize @var{host/webid} @var{#client-id} @var{#redirect-uri} @var{[#state]} @var{[#http-get]}
+The user enters a valid webid or a host name, and then this function will query it (with the @var{http-get} parameter, by default the web client from @code{(web client)}) to determine the authorization endpoint. The function will return an alist of authorization URIs, indexed by approved identity provider URIs, that the user should browse with a traditional web browser.@c fill
+
+Each application should have its own webid, or in that case @var{client-id}, that can be dereferenced by the identity provider.@c fill
+
+Once the user has given authorization, the user’s agent will be redirected to @var{redirect-uri}, with the authorization code as a GET parameter. It is possible to pass a @var{state}, but this is optional.@c fill
+@end deffn
+
+Once the client gets the authorization code, it is necessary to create an access token and ID token.@c fill
+
+@deffn function token @var{host} @var{client-key} @var{[#authorization-code]} @var{[#refresh-token]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+Trade an @var{authorization-code}, or a @var{refresh-token}, for an ID token and an access token bound to the @var{client-key} issued by @var{host}, the identity provider.@c fill
+
+You can override the HTTP client used (@var{http-get} and @var{http-post}), and how to compute the time (@var{current-time}).@c fill
+@end deffn
+
+In an application, you would have a list of profiles in XDG_DATA_HOME, consisting of triples (webid, issuer, refresh token).@c fill
+
+@deffn function list-profiles @var{[#dir]}
+Read the list of available profiles. Returns a list of triples, webid, issuer, reresh token.@c fill
+
+By default, this function will look for the profiles file in XDG_DATA_HOME. You can bypass it by providing the @var{#dir} optional keyword argument.@c fill
+@end deffn
+
+@deffn function setup @var{get-host/webid} @var{choose-provider} @var{browse-authorization-uri} @var{#client-id} @var{#redirect-uri} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+Negociate a refresh token, and save it. The function returns 3 values: the decoded ID token pyload, the encoded access token and the key pair.@c fill
+
+The @var{get-host/webid} thunk should ask the user’s webid or identity provider, and return it. @var{choose-provider} is called with a list of possible identity providers as host names (strings), and the user should choose one. The chosen one is returned. Finally, @var{browse-authorization-uri} should ask or let the user browse an URI as its argument, and return the authorization code taken from the redirect URI.@c fill
+
+The refresh token is saved to disk, as a profile, in XDG_DATA_HOME. Pass the optional @var{#dir} keyword argument to override the location.@c fill
+
+You need to set @var{client-id} to the public webid of the app, and @var{redirect-uri} to one of the approved redirection URIs for the application ID.@c fill
+@end deffn
+
+@deffn function login @var{webid} @var{issuer} @var{refresh-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+If you have already a known profile, you can use it to automatically log in. This function might update the refresh token if it changed, so you can again set @var{#dir}. Please note that the @var{refresh-token} is bound to the client @var{key} on server side, so you must always use the same @var{key}.@c fill
+@end deffn
+
+@deffn function refresh @var{id-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+If you have an ID token bound to a known profile, this helper function will look up the associated refresh token and log in.@c fill
+@end deffn
+
+@deffn function make-client @var{id-token} @var{access-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#http-request]} @var{[#current-time]}
+Return a replacement of @code{http-request} from @code{(web client)}, that automatically signs requests and refresh the tokens when needed.@c fill
+
+@var{#http-get} and @var{#http-post} are only used to refresh the tokens, while @var{#http-request} is used as a back-end for the requests.@c fill
+
+@var{#current-time} is set to a thunk that returns the time. It is used to issue DPoP proofs.@c fill
+@end deffn
+
+An example application is provided as the @code{webid-oidc-example-app} program. It demonstrates how authentication is done. It should help you understand how webid-oidc works.@c fill
+
+The identity provider needs to call the application on the web. So, your client should have a public endpoint on the web.@c fill
+
+@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.@c fill
+@end deffn
+
+The @code{webid-oidc-client-service} program can run a server to serve these resources. It is invoked with the following options:@c fill
+
+@itemize
+@item
+@code{-h}, or @code{--help} prints a summary of the options and exit.@c fill
+@item
+@code{-v}, or @code{--version} prints the version of the program and exits.@c fill
+@item
+@code{-i @var{URI}}, or @code{--client-id=@var{URI}} sets the global identitifier of the application, which is dereferenced to a semantic resource.@c fill
+@item
+@code{-r @var{URI}}, or @code{--redirect-uri=@var{URI}} sets the redirection URI.@c fill
+@item
+@code{-n @var{NAME}}, or @code{--client-name=@var{NAME}} sets the name of your application, so that it is shown when the user gets an authorization. The webid-oidc issuer program that comes with this package does not display it, because it could be dishonest, but other implementations might.@c fill
+@item
+@code{-u @var{URI}}, or @code{--client-uri=@var{URI}}, sets an URI for the identity provider to learn more about your app.@c fill
+@item
+@code{-p @var{PORT}}, or @code{--port=@var{PORT}}, change the port number used by the server. By default, it is set to 8080.@c fill
+@item
+@code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} let the server dump all its output to @var{FILE.log}.@c fill
+@item
+@code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} let the server dump all its errors to @var{FILE.err}.@c fill
+@end itemize
+The program is sensitive to the environment variable @emph{LANG}, which influences how the program is internationalized to the server administrator. This changes the long form of the options, and the language in the log files.@c fill
+
+@node Exceptional conditions
+@chapter Exceptional conditions
+
+The library will raise (new-style) guile exceptions whenever an exceptional condition occurs. For instance, if a signature is invalid, or the expiration date has passed. All exception types are defined in @code{(webid-oidc errors)}.@c fill
+
+@deffn function error->str @var{error} @var{[#depth]}
+Return a string explaining the @var{error}. You can limit the @var{depth} of the explanation as an integer.@c fill
+@end deffn
+
+@menu
+* Invalid data format::
+* Invalid JWT::
+* Cannot fetch data on the web::
+* Other errors in the protocol or from a reasonable implementation::
+@end menu
+
+@node Invalid data format
+@section Invalid data format
+There are a few JSON objects with required fields. This exceptions
+usually occur as the cause of a higher-level exception.
+
+@deftp {exception type} &not-base64 @var{value} @var{cause}
+This exception is raised when the base64 decoding function failed. @var{value} is the incorrect input, and @var{cause} is a low-level error.@c fill
+@end deftp
+
+@deftp {exception type} &not-json @var{value} @var{cause}
+Cannot decode @var{value} to a JSON object.@c fill
+@end deftp
+
+@deftp {exception type} &not-turtle @var{value} @var{cause}
+Cannot decode @var{value} to a RDF graph.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-webid-field @var{value}
+The @var{value} of the webid field in the JWT is missing (if @code{#f}), or not an acceptable value.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-sub-field @var{value}
+The @var{value} of the sub field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-iss-field @var{value}
+The @var{value} of the iss field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-aud-field @var{value}
+The @var{value} of the aud field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-iat-field @var{value}
+The @var{value} of the iat field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-exp-field @var{value}
+The @var{value} of the exp field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-cnf/jkt-field @var{value}
+The @var{value} of the cnf/jkt field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-client-id-field @var{value}
+The @var{value} of the client-id field is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-redirect-uris-field @var{value}
+The @var{value} of the redirect-uris field of a client manifest is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-typ-field @var{value}
+The @var{value} of the typ field in the DPoP proof header is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-jwk-field @var{value} @var{cause}
+The @var{value} of the jwk field in the DPoP proof header is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-jti-field @var{value}
+The @var{value} of the jti field in the DPoP proof is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-nonce-field @var{value}
+The @var{value} of the nonce field in the DPoP proof is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-htm-field @var{value}
+The @var{value} of the htm field in the DPoP proof is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &incorrect-htu-field @var{value}
+The @var{value} of the htu field in the DPoP proof is incorrect.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-client-manifest @var{value} @var{cause}
+The @var{client-manifest} is incorrect.@c fill
+@end deftp
+
+@node Invalid JWT
+@section Invalid JWT
+Each JWT type – access token, DPoP proof, ID token, authorization code
+(this is internal to the identity provider) has different validation
+rules, and can fail in different ways.
+
+@deftp {exception type} &unsupported-crv @var{crv}
+The identifier @var{crv} does not identify an elliptic curve.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-jwk @var{value} @var{cause}
+@var{value} does not identify a JWK.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-public-jwk @var{value} @var{cause}
+@var{value} does not identify a public JWK.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-private-jwk @var{value} @var{cause}
+@var{value} does not identify a private JWK.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-jwks @var{value} @var{cause}
+@var{value} does not identify a set of public keys.@c fill
+@end deftp
+
+@deftp {exception type} &unsupported-alg @var{value}
+@var{value} does not identify a valid hash algorithm.@c fill
+@end deftp
+
+@deftp {exception type} &invalid-signature @var{key} @var{payload} @var{signature}
+@var{key} has not signed @var{payload} with @var{signature}.@c fill
+@end deftp
+
+@deftp {exception type} &missing-alist-key @var{value} @var{key}
+@var{value} isn’t an alist, or is missing a value with @var{key}.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-jws-header @var{value} @var{cause}
+@var{value} does not identify a decoded JWS header.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-jws-payload @var{value} @var{cause}
+@var{value} does not identify a decoded JWS payload.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-jws @var{value} @var{cause}
+@var{value} does not identify a decoded JWS.@c fill
+@end deftp
+
+@deftp {exception type} &not-in-3-parts @var{string} @var{separator}
+@var{string} cannot be split into 3 parts with @var{separator}.@c fill
+@end deftp
+
+@deftp {exception type} &no-matching-key @var{candidates} @var{alg} @var{payload} @var{signature}
+No key among @var{candidates} could verify @var{signature} signed with @var{alg} for @var{payload}, because the signature mismatched for all keys.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-decode-jws @var{value} @var{cause}
+The @var{value} string is not an encoding of a valid JWS.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-encode-jws @var{jws} @var{key} @var{cause}
+The @var{jws} cannot be signed.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-access-token @var{value} @var{cause}
+The @var{value} is not an access token.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-access-token-header @var{value} @var{cause}
+The @var{value} is not an access token header.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-access-token-payload @var{value} @var{cause}
+The @var{value} is not an access token payload.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-decode-access-token @var{value} @var{cause}
+The @var{value} string is not an encoding of a valid access token.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-encode-access-token @var{access-token} @var{key} @var{cause}
+The @var{access-token} cannot be signed.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-dpop-proof @var{value} @var{cause}
+The @var{value} is not a DPoP proof.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-dpop-proof-header @var{value} @var{cause}
+The @var{value} is not a DPoP proof header.@c fill
+@end deftp
+
+@deftp {exception type} &not-a-dpop-proof-payload @var{value} @var{cause}
+The @var{value} is not a DPoP proof payload.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-decode-dpop-proof @var{value} @var{cause}
+The @var{value} string is not an encoding of a valid DPoP proof.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-encode-dpop-proof @var{dpop-proof} @var{key} @var{cause}
+The @var{dpop-proof} cannot be signed.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-authorization-code @var{value} @var{cause}
+The @var{value} is not an authorization code.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-authorization-code-header @var{value} @var{cause}
+The @var{value} is not an authorization code header.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-authorization-code-payload @var{value} @var{cause}
+The @var{value} is not an authorization code payload.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-decode-authorization-code @var{value} @var{cause}
+The @var{value} string is not an encoding of a valid authorization code.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-encode-authorization-code @var{authorization-code} @var{key} @var{cause}
+The @var{authorization-code} cannot be signed.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-id-token @var{value} @var{cause}
+The @var{value} is not an ID token.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-id-token-header @var{value} @var{cause}
+The @var{value} is not an ID token header.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-id-token-payload @var{value} @var{cause}
+The @var{value} is not an ID token payload.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-decode-id-token @var{value} @var{cause}
+The @var{value} string is not an encoding of a valid ID token.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-encode-id-token @var{id-token} @var{key} @var{cause}
+The @var{id-token} cannot be signed.@c fill
+@end deftp
+
+@node Cannot fetch data on the web
+@section Cannot fetch data on the web
+In the client (local and public parts), resource server and identity
+provider, the protocol requires to fetch data on the web.
+
+@deftp {exception type} &request-failed-unexpectedly @var{response-code} @var{response-reason-phrase}
+We expected the request to succeed, but the server sent a non-OK @var{response-code}.@c fill
+@end deftp
+
+@deftp {exception type} &unexpected-header-value @var{header} @var{value}
+We did not expect the server to respond with @var{header} set to @var{value}.@c fill
+@end deftp
+
+@deftp {exception type} &unexpected-response @var{response} @var{cause}
+The @var{response} (from @code{(web response)}) is not appropriate.@c fill
+@end deftp
+
+@deftp {exception type} &not-an-oidc-configuration @var{value} @var{cause}
+The @var{value} is not an OIDC configuration.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-fetch-issuer-configuration @var{issuer} @var{cause}
+It is impossible to fetch the configuration of @var{issuer}.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-fetch-jwks @var{issuer} @var{uri} @var{cause}
+It is impossible to fetch the keys of @var{issuer} at @var{uri}.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-fetch-linked-data @var{uri} @var{cause}
+Could not fetch the graph referenced by @var{uri}.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-fetch-client-manifest @var{id} @var{cause}
+Could not fetch a client manifest at @var{id}.@c fill
+@end deftp
+
+@node Other errors in the protocol or from a reasonable implementation
+@section Other errors in the protocol or from a reasonable implementation
+The protocol does not rely solely on JWT validation, so these errors
+may happen too.
+
+@deftp {exception type} &dpop-method-mismatch @var{signed} @var{requested}
+The method value @var{signed} in the DPoP proof does not match the method that is @var{requested} on the server.@c fill
+@end deftp
+
+@deftp {exception type} &dpop-uri-mismatch @var{signed} @var{requested}
+The URI value @var{signed} in the DPoP proof does not match the URI that is @var{requested} on the server.@c fill
+@end deftp
+
+@deftp {exception type} &dpop-signed-in-future @var{signed} @var{current}
+The proof is @var{signed} for a date which is too much ahead of the @var{current} time.@c fill
+@end deftp
+
+@deftp {exception type} &dpop-too-old @var{signed} @var{current}
+The proof was @var{signed} at a past date of @var{current}.@c fill
+@end deftp
+
+@deftp {exception type} &dpop-unconfirmed-key @var{key} @var{expected} @var{cause}
+The confirmation of @var{key} is not what is @var{expected}, or (if a function was passed as @var{cnf/check}) the @var{cause} exception occurred while confirming.@c fill
+@end deftp
+
+@deftp {exception type} &jti-found @var{jti} @var{cause}
+The @var{jti} of the proof has already been issued in a recent past.@c fill
+@end deftp
+
+@deftp {exception type} &unauthorized-redirection-uri @var{manifest} @var{uri}
+The authorization @var{uri} is not advertised in @var{manifest}.@c fill
+@end deftp
+
+@deftp {exception type} &cannot-serve-public-manifest
+You cannot serve the public client manifest.@c fill
+@end deftp
+
+@deftp {exception type} &no-client-manifest-registration @var{id}
+The @var{id} client manifest does not have a registration triple in its document.@c fill
+@end deftp
+
+@deftp {exception type} &inconsistent-client-manifest-id @var{id} @var{advertised-id}
+The client @var{manifest} is being fetched at @var{id}, but it is valid for another client @var{advertised-id}.@c fill
+@end deftp
+
+@deftp {exception type} &authorization-code-expired @var{exp} @var{current-time}
+The authorization code has expired at @var{exp}, it is now @var{current-time}.@c fill
+@end deftp
+
+@deftp {exception type} &invalid-refresh-token @var{refresh-token}
+The @var{refresh-token} is unknown to the identity provider.@c fill
+@end deftp
+
+@deftp {exception type} &invalid-key-for-refresh-token @var{key} @var{jkt}
+The refresh token was issued for @var{jkt}, but it is used with @var{key}.@c fill
+@end deftp
+
+@deftp {exception type} &unknown-client-locale @var{web-locale} @var{c-locale}
+The @var{web-locale} of the client, translated to C as @var{c-locale}, cannot be set. This exception is always continuable; if the handler returns, then the page will be served in the english locale.@c fill
+@end deftp
+
+@deftp {exception type} &unsupported-grant-type @var{value}
+The token request failed to indicate a @var{value} for the grant type, or indicated an unsupported grant type.@c fill
+@end deftp
+
+@deftp {exception type} &no-authorization-code
+The token request forgot to put an authorization code.@c fill
+@end deftp
+
+@deftp {exception type} &no-refresh-token
+The token request forgot to put a refresh token with the request.@c fill
+@end deftp
+
+@deftp {exception type} &unconfirmed-provider @var{subject} @var{provider}
+@var{provider} is not confirmed by @var{subject} as an identity provider.@c fill
+@end deftp
+
+@deftp {exception type} &no-provider-candidates @var{webid} @var{causes}
+The @var{webid} cannot be certified by any identity providers. The @var{causes} alist indicates an error for each candidates.@c fill
+@end deftp
+
+@deftp {exception type} &neither-identity-provider-nor-webid @var{uri} @var{why-not-identity-provider} @var{why-not-webid}
+The @var{uri} you passed to get an authorization code is neither an identity provider (because @var{why-not-identity-provider}) nor a webid (because @var{why-not-webid}).@c fill
+@end deftp
+
+@deftp {exception type} &token-request-failed @var{cause}
+The token request failed on the server.@c fill
+@end deftp
+
+@deftp {exception type} &profile-not-found @var{webid} @var{iss} @var{dir}
+The @var{webid}, as certified by @var{iss}, cannot be refreshed by the client because we don’t have a refresh token stored in @var{dir}.@c fill
+@end deftp
+
+@node GNU Free Documentation License
+@appendix GNU Free Documentation License
+
+@include fdl.texi
+
+@node Index
+@unnumbered Index
+
+@printindex cp
+
+@bye
diff --git a/guix/vkraus/packages/webid-oidc.scm b/guix/vkraus/packages/webid-oidc.scm
index 79e9d11..889e7f3 100644
--- a/guix/vkraus/packages/webid-oidc.scm
+++ b/guix/vkraus/packages/webid-oidc.scm
@@ -79,8 +79,7 @@
("gettext" ,gnu-gettext)
("coreutils" ,coreutils) ;; for link (wrap-program)
("help2man" ,help2man)
- ("which" ,which)
- ("libxslt" ,libxslt)))
+ ("which" ,which)))
(inputs `(("guile" ,guile-3.0)
("nettle" ,nettle)))
(propagated-inputs