diff options
Diffstat (limited to 'yarns/300-end-user-auth.yarn')
-rw-r--r-- | yarns/300-end-user-auth.yarn | 153 |
1 files changed, 146 insertions, 7 deletions
diff --git a/yarns/300-end-user-auth.yarn b/yarns/300-end-user-auth.yarn index 46d6236..e69ccad 100644 --- a/yarns/300-end-user-auth.yarn +++ b/yarns/300-end-user-auth.yarn @@ -7,39 +7,178 @@ subset of that. It's just enough for us to have some form of login, to set up a continuous delivery pipeline for it, and to start building the full thing. -FIXME: Explain the login process here, with sequence diagram. +OpenID Connect +----------------------------------------------------------------------------- + +[OpenID Connect]: https://openid.net/specs/openid-connect-core-1_0.html +[OAuth2]: https://tools.ietf.org/html/rfc6749 + +[OpenID Connect][] (OIDC) is a standard protocol for authenticating +end-users. It is an extension of the [OAuth2][] protocol, which +provides authorization. The distinction is important: OAuth2 allows a +user let an application access the user's data on some service, but +does not itself provide a way to verify the user's identity. OIDC +provides a way for the service to do the identity verification +(authentication). + +There are, in the OIDC context, several entities involved: + +* The **end-user**, who owns some data on the resource server. Ownership + here is in the legal sense. + +* The end-user's user agent, also known as the browser. This is what + the end-user interacts with directly. + +* The **resource server**, where the actual data is. It is assumed the + data is sensitive, and needs to be protected, but that some access + should be allowed. + +* The **facade application**, which accesses data on the resource + server, and processes it in some way for the user's benefit. + +* The **OpenID provider** authenticates the user and produces tokens + for the facade application to access the user's data on the resource + server. This is Qvisqve. + +The OIDC authorization code flow works like this, at a very high +level: + +* User initiates an authentication process. Typically by clicking on a + login link, but might also happen when the facade notices + authentication is needed, such as when its token expires. In either + case, there is an HTTP request from the browser to the facade. + +* The facade returns a 302 redirect to the browser. The redirect takes + the browser to the OpenID provider, and supplies a number of + parameters in the URL to identify the application, and what access + it wants. + +* The OpenID provider verifies the user's identify, such as by asking + them for their username and password. + +* Once the identity is verified, the OpenID provider redirects the + browser back to the facade, and gives a unique, single-use + authorization code as part of the redirected URL. + +* The facade extracs the authorization code, and requests an access + token from the OpenID provider. As part of that, the facade + authenticates itself to the OpenID provider. + +* The facade uses the access token to get data from the resource + server, and shows it to the user via the browser. + +Note that the access token never reaches the browser. Also, it is +given only to an authenticated facade application. Further, the facade +application never sees the user's login creentials. There's more +details in the protocol to mitigate replay attack, cross-site +forgeries and other shenanigans. + +Test scenario +----------------------------------------------------------------------------- + +This scenario shows the steps of authenticating the end-user. SCENARIO end-user interactive login +We need to have a Qvisqve, and there needs to be a user account +configured for it. Further, the facade application must also be +registerd, before the login process starts. + GIVEN a Qvisqve configuration for "https://qvisqve" AND Qvisqve configuration has user account tomjon with password hunter2 AND Qvisqve configuration has application facade ... with callback url https://facade/callback + ... and secret happydays + ... and allowed scopes read write AND a running Qvisqve instance -User goes to the login URL and gets a login page. +User goes to the facade's login URL and gets a redirect to Qvisqve's +/auth endpoint. We skip the request to the facade. The redirect to +/auth includes important parameters. The parameters are: + +* `response_type=code` — identify this as the start of an + authorization code flow +* `scope=openid+read` — space delimited list of scope names + required by the facade, plus `openid` to indicate we want OIDC +* `client_id=facade` — identify the application; note that at + this point Qvisqve can't verify this +* `state=RANDOM`— a unique, single-use, hard-to-guess value +* `redirect_uri=https://facade/callback` — where the browser + should go when the user has been identified - WHEN browser requests GET /login +Since the request to Qvisqve is always done as a result of an HTTP 302 +redirect response from the facade, it's always a GET request, and the +parameters are part of the URL. The response should be login form. The +form will have a hidden field, with which Qvisqve will know which +authentication attempt is happening. This field will have a new, +unique, hard-to-guess value every time the user authenication starts +anew. Note that this should probably be different from the `state` +value from the facade (FIXME: why?). + + WHEN browser requests GET /auth?response_type=code&scope=openid+read&client_id=facade&state=RANDOM&redirect_uri=https://facade/callback THEN HTTP status code is 200 OK AND Content-Type is text/html AND body has an HTML form with field username AND body has an HTML form with field password + AND body has an HTML form with field attempt_id + AND HTML form field attempt_id is saved as ATTEMPTID + +The user enters their login credentials, and presses submit. Qvisqve +verifies the credentials. WHEN browser requests POST /auth, with form values - ... username=tomjon and password=wrong + ... username=tomjon and password=wrong and attempt_id=${ATTEMPTID} THEN HTTP status code is 401 Unauthorized +FIXME: a 401 error is unfriendly, the user should be given an +opportunity to try again. Fix later. Anyway, let's say user can use +the back button and tries again with the correct password. + +Qvisqve accepts the credentials this time, and generates an +"authorization code". This is a unique, hard-to-predict value that can +be used only once. The code is added to the facade callback URL, and +the browser is redirected to that. This is how the facade gets the +code. The browser also gets the code, but since using the code +requires the facade's client credentials, the browser can't use the +authorization code for anything, so it's acceptably safe to let the +browser see it. + WHEN browser requests POST /auth, with form values - ... username=tomjon and password=hunter2 + ... username=tomjon and password=hunter2 and attempt_id=${ATTEMPTID} THEN HTTP status code is 302 Found - AND HTTP Location header is https://facade/callback?code=123 + AND HTTP Location header starts with https://facade/callback? + AND HTTP Location header is saved as LOCATION + AND authorization code from LOCATION is saved as CODE + +The browser follows the redirect to the facade. The facade extracts +the authorization code, and uses its own client credentials to +retrieve the access token corresponding to the code. + + WHEN facade requests POST /token, with + ... form values grant_type=authorization_code and code=${CODE} + ... using Basic Auth with username facade, password wrong + THEN HTTP status code is 401 Unauthorized WHEN facade requests POST /token, with - ... form values grant_type=authorization_code and code=123 + ... form values grant_type=authorization_code and code=${CODE} + ... using Basic Auth with username facade, password happydays + +Qvisqve returns the access token. It has the requested scope, minus +"openid". + +FIXME: The aud field should have the facade as one of the values, but +the resource server needs to also be there. Or else the resource +server needs to ignore aud, which seems fishy, or it needs to accept +the facade as the aud, which seems tricky. I don't know how to handle +this. Needs research and thinking. + THEN HTTP status code is 200 OK AND Content-Type is application/json AND JSON body has field access_token AND JSON body has field token_type, with value Bearer AND JSON body has field expires_in + AND access token has a scope field set to read + AND access token has a sub field set to tomjon + FINALLY Qvisqve is stopped |