summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-09-12 22:57:58 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-09-14 16:06:43 +0200
commit328b4957d05fc9b0f9ff87f2a4932ae0296ab069 (patch)
tree2d44b7896c91f9934b470fd6bb54141ddc4dc714 /doc
parent6a83b79c4de5986ad61a552c2612b7cce0105cda (diff)
Restructure the client API
The client API had several problems: - using records instead of GOOPS means that we aren’t flexible enough to introduce accounts protected by a password, for a multi-user application; - saving the user database to disk means we can’t have a proper immutable API; - it was difficult to predict when the users database would change, and inform the user interface about this change; - it had two different ways to negociate an access token, one when we had a refresh token and one when we did not; - it was supposed to either use account objects or a subject / issuer pair, now we only use account objects.
Diffstat (limited to 'doc')
-rw-r--r--doc/disfluid.texi248
1 files changed, 153 insertions, 95 deletions
diff --git a/doc/disfluid.texi b/doc/disfluid.texi
index 04e69af..cf413af 100644
--- a/doc/disfluid.texi
+++ b/doc/disfluid.texi
@@ -864,81 +864,160 @@ authorization. Otherwise, raise an exception of type
@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.
-
-The list of accounts is stored on the file system. You can manipulate
-the accounts with the @emph{(webid-oidc client accounts)} module.
-
-@deftp {Record type} <account> @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{keypair}
-Store information about an account. @var{subject} is optional,
-@var{issuer} is required, but they must both be URIs. In a typical
-application, you would ask the user for per @var{issuer}, without
-bothering perse with a webid (it can be long to type), and then start
-making requests with this account. When you need an authorization
-code, you will know the user’s webid.
-
-If the access token was not invalidated, then @var{id-token} contains
-a (decrypted) identity token, and @var{access-token} an encrypted
-access token. If you got a @var{refresh-token} for this account, it is
-also stored, along with the @var{keypair} that is server-side bound to
-it.
-
-The optional parameters are @code{#f} when we don’t have them.
+The job of the client is to use accounts to fetch private resources on
+the web. The @emph{(webid-oidc client)} defines the @code{<client>}
+class.
+
+@deftp {Class} <client> @var{client-id} @var{key-pair} @var{redirect-uri}
+In OIDC, a client is an application that does not hold the
+resources. It may in fact be a network server available on the web, or
+a program that you run on your machine. Being a network server or not
+is irrelevant.
+
+The @code{<client>} class is designed with immutability in mind. You
+can create a client with the @code{make} generic method, using these
+keywords to initialize values:
+
+@table @code
+@item #:client-id
+to set the public client identifier (this endpoint
+should be available on the world-wide web), as a string representing
+an URI or an URI from @code{(web uri)};
+@item #:key-pair
+to use a specific key pair. If not set, a new key pair will be
+generated;
+@item #:redirect-uri
+to set the redirect URI that the application controls. It may just be
+a page showing the authorization code, with instructions on how to
+paste this code into the application. It should match one of the
+authorized redirect URIs in the client identifier endpoint.
+
+If you want to set a state parameter for the redirection, you can do
+it by setting the guile parameter @code{authorization-state}.
+@end table
@end deftp
-@deffn function make-account @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{keypair}
-Create an account.
-@end deffn
+@deftypefn {Generic method} uri client-id (@var{client} @code{<client>})
+@deftypefnx {Generic method} {key pair} client-key-pair (@var{client} @code{<client>})
+@deftypefnx {Generic method} uri client-redirect-uri (@var{client} @code{<client>})
+Slot accessors for @var{client}.
+@end deftypefn
-@deffn function account? @var{object}
-Check whether @var{object} is an account.
-@end deffn
+@defvr {Parameter} client
+Define this parameter to set the client to use to access private data.
+@end defvr
-@deffn function account-subject @var{account}
-@deffnx function account-issuer @var{account}
-@deffnx function account-id-token @var{account}
-@deffnx function account-access-token @var{account}
-@deffnx function account-refresh-token @var{account}
-@deffnx function account-keypair @var{account}
-Access the account.
-@end deffn
+To access private data, you must identify yourself. The
+@emph{(webid-oidc client accounts)} module lets you define accounts.
+
+@deftp {Class} <account> @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{key-pair}
+Encapsulate an account. @var{subject} is your webid, while
+@var{issuer} is a host name. @var{id-token} is the @emph{decoded} OIDC ID token, i.e. a
+pair of @code{(header . payload)}, because we don’t need to show it to
+any other party, so its authenticity needs not be
+demonstrated. However, @var{access-token} is an @emph{encoded} access
+token (into a string), because we don’t need to worry about its
+internals on client side.
+
+There are different ways to initialize an account. First, you can save
+all parameters to some form of storage, and restore it by using the
+associated keyword arguments at construction time:
+
+@table @code
+@item #:subject
+@item #:issuer
+@item #:id-token
+@item #:access-token
+@item #:refresh-token
+@item #:key-pair
+@end table
-You should always manage the accounts with the users database.
+If you want to make a new account, you would ask the user for an
+identity provider, and pass it with @code{#:issuer} as the only
+initialized value. The constructor will log you in, using the
+@code{authorization-process} and @code{anonymous-http-request}
+function parameters.
-@deffn function read-accounts
-Read the list of all accounts. This function is safe to call at any
-time during concurrent updates of the database. If the update was
-finished, the new list is returned, otherwise the old list is returned
-without blocking.
-@end deffn
+If you want to refresh an access token, you would also set
+@code{#:refresh-token}.
-@deffn function save-account @var{account}
-Find an account in the database with the same subject and issuer, and
-replace its contents with @var{account}. Return @var{account}.
-@end deffn
+In any case, when you don’t specify a value, it’s as if you passed
+@code{#f}.
+@end deftp
-@deffn function delete-account @var{account}
-Remove all accounts from the database that have the same subject and
-issuer as @var{account}.
-@end deffn
+@defvr {Parameter} authorization-process
+This function is called when an explicit user authorization is
+required, for instance because there is no refresh token and the
+access token expired. The function takes an URI as argument, with an
+additional @code{#:issuer} keyword argument containing the issuer. In
+this function, you should ask the user to browse this URI so that your
+application gets the authorization code.
+@end defvr
-To log a user in, you must know at least per issuer. More precisely,
-if the user is already known (because, for instance, the user presents
-a cookie for your web application), then you should know the user’s
-webid and the webid issuer. If you don’t know the user, and the user
-is eligible to your service, then you will only know the identity
-provider (issuer), because that’s what the user typed in.
+@defvr {Parameter} anonymous-http-request
+This function is used as a back-end for private resource access, and
+to query the server configuration. It defaults to @code{http-request}
+from @emph{(web client)}.
+@end defvr
-@deffn function login @var{subject} @var{issuer} [#:@var{http-request}=@code{http-request}] [#:@var{state}=@code{#f}] #:@var{client-id} #:@var{client-key} #:@var{redirect-uri}
-Return a new account with an ID token and an access
-token. @var{subject} is optional.
+@deftypefn {Generic method} uri subject (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-subject (@var{account} @code{<account>}) (@var{uri} {string or URI})
+@deftypefnx {Generic method} uri issuer (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-issuer (@var{account} @code{<account>}) (@var{uri} {string or URI})
+@deftypefnx {Generic method} {optional decoded ID token} id-token (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-id-token (@var{account} @code{<account>}) (@var{id-token} {optional ID token})
+@deftypefnx {Generic method} {optional encoded access token} access-token (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-access-token (@var{account} @code{<account>}) (@var{access-token} {optional access token})
+@deftypefnx {Generic method} {optional <string>} refresh-token (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-refresh-token (@var{account} @code{<account>}) (@var{refresh-token} {optional <string>})
+@deftypefnx {Generic method} {key pair} key-pair (@var{account} @code{<account>})
+@deftypefnx {Generic method} <account> set-key-pair (@var{account} @code{<account>}) (@var{key-pair} {optional key pair})
+Slot accessors and functional setters for @var{account}.
+@end deftypefn
+
+If you intend to run a public network server as a client application,
+you may have multiple different users, but you should not let any user
+use any account. If this is the case, you can either store the
+accounts on the user agent storage (for instance, as a cookie), or
+store all of them on the server. If you choose to store the accounts
+on the user agent, at least use a new key pair for each of them. If
+you want to store the user database on the server side, be aware that
+no entity other than yourself will check that your user abides by any
+term of service, so it is possible that a single user makes a lot of
+accounts to annoy you and fill your hard drive with key pairs. If your
+application does not let random people to use it, you might want to
+use @emph{protected accounts}, to help you check that the users cannot
+impersonate each other.
+
+@deftp {Class} <protected-account> (@code{<account>}) @var{username} @var{encrypted-password}
+This superclass of @code{<account>} is protected by a username and
+password. It is constructed with the initializer keywords
+@code{#:username} and @code{#:encrypted-password}.
+@end deftp
-When you receive an account record from this function, make sure to
-save it to the accounts database with @code{save-account}.
-@end deffn
+@deftypefn {Generic method} <string> username (@var{protected-account} @code{<protected-account>})
+@deftypefnx {Generic method} <protected-account> set-username (@var{protected-account} @code{<protected-account>}) (@var{username} <string>)
+@deftypefnx {Generic method} <string> encrypted-password (@var{protected-account} @code{<protected-account>})
+@deftypefnx {Generic method} <protected-account> set-encrypted-password (@var{protected-account} @code{<protected-account>}) (@var{encrypted-password} <string>)
+Slot accessors and functional setters for @var{protected-account}.
+@end deftypefn
+
+@deftypefn {Generic method} <account> invalidate-access-token (@var{account} @code{<account>})
+Indicate that the access token in @var{account} cannot be used. Before
+using @var{account} again, you will need to refresh the access
+token. This function does not mutate @var{account}.
+@end deftypefn
+
+@deftypefn {Generic method} <account> invalidate-refresh-token (@var{account} @code{<account>})
+Indicate that the refresh token has been revoked for
+@var{account}. This is usually an indication that the user don’t want
+your application to access her private data. This function does not
+mutate @var{account}.
+@end deftypefn
+
+@deftypefn {Generic method} <account> refresh (@var{account} @code{<account>})
+Refresh the access token.
+@end deftypefn
@deftp {Exception type} &authorization-code-required @var{uri}
If the login process requires the user to send an authorization code,
@@ -1007,40 +1086,19 @@ Constructor, predicate, and accessors for the
@code{&token-request-failed} exception type.
@end deffn
-The @emph{(webid-oidc client)} module provides support for complete
-clients.
-
-@deftp {Record type} <client> @var{id} @var{key} @var{redirect-uri}
-The @var{id} of a client is an URI without fragment that can be
-dereferenced in the world-wide web to metadata about the client. It
-should allow @var{redirect-uri} to access the authorization code.
+The @emph{(webid-oidc client)} module provides the most useful
+function for a client.
-It is useful if an application rotates its @var{key}. So, while a key
-will still be used as long as the associated refresh token for a given
-user is valid, you can equip new users with a new key pair.
-@end deftp
-
-@deffn function make-client @var{id} @var{key} @var{redirect-uri}
-@deffnx function client? @var{object}
-@deffnx client-id @var{object}
-@deffnx client-key @var{object}
-@deffnx client-redirect-uri @var{object}
-Constructor, predicate and accessors for the @code{<client>} record
-type.
+@deffn function request @var{account} @var{uri} . @var{args}
+Perform a request on behalf of @var{account}, with the current value
+of the @var{client} parameter as the client, using as a backend the
+current value of @var{anonymous-http-request}.
@end deffn
-@deffn function initial-login @var{client} @var{issuer} [#:@var{http-request}]
-Create an account by logging in with just the @var{issuer}, and save
-the created account. The default @var{http-request} uses the cache for
-GET requests.
-@end deffn
-
-@deffn function request @var{client} @var{subject} @var{issuer} [#:@var{http-request}]
-Log in with @var{subject} (optional, may be @code{#f}) and
-@var{issuer}, and return a function that takes a request and request
-body and transfers it, signed, to the @var{http-request} back-end. By
-default, it uses the cache for GET requests.
-@end deffn
+Finally, to implement your application, there needs to be a public
+endpoint for the resource server to check that you are not
+impersonating another application. This endpoint can be served by any
+web server, but a convenience procedure is made available here:
@deffn function serve-application @var{id} @var{redirect-uri} @var{[#client-name]} @var{[#client-uri]}
Return a handler for web requests to serve the application manifest