From db55d55e5c36c940986f437d26da1ff3c601c3b4 Mon Sep 17 00:00:00 2001 From: Vivien Kraus Date: Sat, 7 Aug 2021 22:45:06 +0200 Subject: Make a better client API --- doc/disfluid.texi | 212 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 137 insertions(+), 75 deletions(-) (limited to 'doc') diff --git a/doc/disfluid.texi b/doc/disfluid.texi index 93128c1..2841052 100644 --- a/doc/disfluid.texi +++ b/doc/disfluid.texi @@ -443,8 +443,8 @@ Get the corresponding field of the proof. Check and decode a DPoP proof encoded as @var{str}. 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. +string that is remembered globally until its expiration date is +reached. The proof is limited to the scope of one @var{uri} and one @var{method} (@code{'GET}, @code{'POST} and so on). @@ -873,103 +873,165 @@ 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 first operation is performed by the @emph{(webid-oidc client)} -module. +The list of accounts is stored on the file system. You can manipulate +the accounts with the @emph{(webid-oidc client accounts)} module. -@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 @emph{(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. +@deftp {Record type} @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{keypair} +Store information about an account. @var{subject} and @var{issuer} are +required, they must bue URIs. 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. -Each application should have its own webid, or in that case -@var{client-id}, that can be dereferenced by the identity provider. +The optional parameters are @code{#f} when we don’t have them. +@end deftp -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. +@deffn function make-account @var{subject} @var{issuer} @var{id-token} @var{access-token} @var{refresh-token} @var{keypair} +Create an account. @end deffn -Once the client gets the authorization code, it is necessary to create -an access token and ID token. +@deffn function account? @var{object} +Check whether @var{object} is an account. +@end deffn -@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. +@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 -You can override the HTTP client used (@var{http-get} and -@var{http-post}), and how to compute the time (@var{current-time}). +You should always manage the accounts with the users database. + +@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 -In an application, you would have a list of profiles in XDG_DATA_HOME, -consisting of triples (webid, issuer, 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 -@deffn function list-profiles @var{[#dir]} -Read the list of available profiles. Returns a list of triples, webid, -issuer, reresh token. +@deffn function delete-account @var{account} +Remove all accounts from the database that have the same subject and +issuer as @var{account}. +@end deffn + +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. -By default, this function will look for the profiles file in -@var{XDG_DATA_HOME}. You can bypass it by providing the @var{#dir} -optional keyword argument. +@deffn function login @var{subject} @var{issuer} [#:@var{http-get}=@code{http-get}] [#:@var{http-post}=@code{http-post}] [#:@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. + +When you receive an account record from this function, make sure to +save it to the accounts database with @code{save-account}. @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. - -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. - -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. - -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. +@deftp {Exception type} &authorization-code-required @var{uri} +If the login process requires the user to send an authorization code, +an exception of this type will be raised, with an implicit invitation +for the user to browse @var{uri} and follow the instructions. + +The instructions will be handled by the @var{redirect-uri} in the +@code{login} function. If your client is a traditional web +application, the user will be redirected to this URI with an +authorization code. If your client is a native application, then maybe +that redirection URI should display the authorization code and invite +the user to paste it in the appropriate place in the application. + +When an exception of this type is raised during the @code{login} +function, it is continuable, meaning that the login function will +resume. You need to create an exception handler for an exception of +this type, look up the @var{uri}, direct the user to browse it, get +the authorization code back, and @emph{return} the authorization code +@emph{from the exception handler}. +@end deftp + +@deffn function make-authorization-code-required @var{uri} +@deffnx function authorization-code-required? @var{error} +@deffnx function authorization-code-required-uri @var{error} +Constructor, predicate, and accessor for the +@code{&authorization-code-required} exception type. @end deffn -@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}. +@deftp {Exception type} &refresh-token-expired +The refresh token can be used to still perform requests on behalf of +the user when perse is offline. However, if the refresh token expires +while the user is offline, it is not possible to log in again, because +it requires a new authorization code. So, it is not possible to +recover from this error, and the refresh token is immediately +discarded. +@end deftp + +@deffn function make-refresh-token-expired +@deffnx function refresh-token-expired? @var{error} +Constructor and predicate for the @code{&refresh-token-expired} +exception type. @end deffn -@deffn function 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. +@deffn function invalidate-access-token @var{account} +Discard the access token for @var{account}. It is not saved in the +user database yet. This is roughly equivalent to log out. @end deffn -@deffn function 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 @emph{(web client)}, -that automatically signs requests and refresh the tokens when needed. +@deffn function invalidate-refresh-token @var{account} +Discard the refresh token for @var{account}. You still need to save +the @var{account}. +@end deffn -@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. +@deftp {Exception type} &token-request-failed @var{response} @var{response-body} +If the token endpoint is unable to deliver an identity token and an +access token, this exception is raised with the identity provider +@var{response} and @var{response body}. This exception cannot be +continued. +@end deftp -@var{#current-time} is set to a thunk that returns the time. It is -used to issue DPoP proofs. +@deffn function make-token-request-failed @var{response response-body} +@deffnx function token-request-failed? @var{error} +@deffnx function token-request-response @var{error} +@deffnx function token-request-response-body @var{error} +Constructor, predicate, and accessors for the +@code{&token-request-failed} exception type. @end deffn -An example application is provided as the -@code{disfluid-example-app} program. It demonstrates how -authentication is done. It should help you understand how Solid-OIDC -works. +The @emph{(webid-oidc client)} module provides support for complete +clients. -The identity provider needs to call the application on the web. So, -your client should have a public endpoint on the web. +@deftp {Record type} @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. + +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{} record +type. +@end deffn + +@deffn function request @var{client} @var{subject} @var{issuer} [#:@var{http-request}=@code{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. +@end deffn @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 -- cgit v1.2.3