summaryrefslogtreecommitdiff
path: root/yarns/300-end-user-auth.yarn
diff options
context:
space:
mode:
Diffstat (limited to 'yarns/300-end-user-auth.yarn')
-rw-r--r--yarns/300-end-user-auth.yarn153
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