# Overview Yuck may some day become an OpenID Connect and OAuth server. This is just a sketcth for what it might look like. ## Concepts Some basic concepts in this document: * identity: data about who you are to tell you apart from everyone else * authentication: proving your identity * authorization: giving permission to do something ## The protocols: OAuth and OpenID Connect [OpenID Connect]: https://openid.net/specs/openid-connect-core-1_0.html [OAuth]: https://tools.ietf.org/html/rfc6749 The [OAuth][] 2.0 protocol is for authorization, not authentication, and assumes an already existing way to authenticate users. It's mainly for giving a service or application permission to do something on your behalf. The [OpenID Connect][] 1.0 (OIDC) protocol is for authenticating yourself to one service or application by using a third party service. This allows one authentication service (or identity provider) be used for any number of other services or applications. Further, since the identity provider can keep a login session open independently of the other services and applications, this provides a single sign-on experience. ## Entities involved in the protocols The protocols involves the following entities: * the **end user**, who is trying to do something; also the resource owner * the **web browser**, used by the user; might be a mobile or command line application instead of a browser per se * the **application**, which the user uses to do things, and as part of that access resources * the **resource provider**, where the resources are, and which allows access to them via a web API * the **identity provider** (IDP), which authenticates the user # The protocols Yuck only supports specific versions and subsets of the protocols it implements, chosen based mainly from the point of security. FIXME: expand this with explanations of why the specific choices have been made and why other subsets (grants, flows) are not chosen. ## The OAuth 2.0 protocol: client credentials grant See [RFC8252][] for a description of the client credentials grant. [RFC8252]: https://tools.ietf.org/html/rfc8252 ## The OIDC 1.0 protocol: authorization code This augments the plain OIDC with cookies: * an **app cookie** set by the application to tie a user and their session together: this lets the application store data about the user and what they're doing between HTTP requests * a **login cookie** set by the IDP to remember this user has a valid login session ### Successful resource access by a logged-out user ~~~plantuml @startuml actor "End user" as user participant browser as "User agent \n (web browser)" participant app as "Application \n (web, mobile)" participant idp as "Identity provider" database rp as "Resource provider" user -> browser : clicks on something note left User initiates an action that requires a protected resource from the resource provider. endnote browser -> app : GET /something note left Browser asks the application for what the user asked for. end note browser <- app : redirect to IDP /login?... note right Application does not have a session open for user. It redirects browser to IDP for login and sets an **app cookie**. It remember what resource the user was asking for. end note browser -> idp : GET /login?... note left Browser follows redirect. The URL contains the application's client id, callback URL, and other information the IDP will need later. end note browser <- idp: login page note right IDP has no login session for user (no **login cookie**). It creates an internal authentication process record. end note user <- browser : show login form user -> browser : enter login info browser -> idp : POST login form note left URL includes identifier for authentication record. end note browser <- idp : redirect to app /callback?code=... note right IDP checks credentials, generates **authorization code**, stores it in the authentication process record and in the redirect URL. IDP sets **login cookie**. end note browser -> app : GET /callback?code=... note left Browser follows redirect, includes **app cookie**. end note app -> idp : request tokens using authz code note left Application gets access, id, and refresh tokens from IDP, using the **authorization code** it just got. Application uses Basic Auth to prove which application it is. end note app <- idp : access, id, and refresh tokens note right IDP checks applicaion credential and generates the tokens. IDP forgets the code (it's single-use only). end note note left Application stores the cookies so they can be found via the **app cookie**. end note app -> rp : access resource with access token note left Application gets the resource the user originally asked for. It remembers what it was thank so the **app cookie**. end note app <- rp : resource browser <- app : page with resource user <- browser : show what the user wanted @enduml ~~~ ### Successful resource access by a logged-in user ~~~plantuml @startuml actor "End user" as user participant browser as "User agent \n (web browser)" participant app as "Application \n (web, mobile)" participant idp as "Identity provider" database rp as "Resource provider" user -> browser : clicks on something note left User initiates an action that requires a protected resource from the resource provider. endnote browser -> app : GET /something note left Browser asks the application for what the user asked for. The **app cookie** is included in the request. end note app -> rp : access resource with access token note left Application gets the resource using the **access token** it has associated with the **app cookie**. end note app <- rp : resource browser <- app : page with resource user <- browser : show what the user wanted @enduml ~~~ ### Successful request when an access cookie has expired ~~~plantuml @startuml actor "End user" as user participant browser as "User agent \n (web browser)" participant app as "Application \n (web, mobile)" participant idp as "Identity provider" database rp as "Resource provider" user -> browser : clicks on something note left User initiates an action that requires a protected resource from the resource provider. endnote browser -> app : GET /something note left Browser asks the application for what the user asked for. The **app cookie** is included in the request. end note app -> rp : access resource with access token note left Application gets the resource using the **access token** it has associated with the **app cookie**. end note app <- rp : access denied note right The RP sees the **access token** is expired and returns an error. end note app -> idp : request new access cookie note left Application uses the **refresh token** to get a **new access token**. end note app <- idp : new access token note left Application stores **new access token** in user's session. end note app -> rp : access resource with new access token note left Application gets the resource using the **new access token**. end note app <- rp : resource browser <- app : page with resource user <- browser : show what the user wanted @enduml ~~~ ### FIXME: unhappy paths # Acceptance criteria This chapter captures the acceptance criteria for Yuck in the form of *scenarios* using the Subplot language. Subplot allows Yuck to be automatically verified against the criteria. ## Server reports its version ~~~scenario given a running Yuck at https://auth.example.com when I do GET /version without a token then response status code is 200 ~~~ ## OAuth2 client credentials grant An example request: ~~~ POST /token HTTP/1.1 Authorization: Basic USERPASS Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=foo+bar+foobar ~~~ The `USERPASS` has the client id and secret encoded as is usual for [HTTP Basic authentication][]. [HTTP Basic authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication Yuck checks the client id and secret against known, pre-registered clients, and that the `grant_type` URL parameter is `client_credential`, and returns a signed JWT access token, if all is OK. ~~~json { "iss": "https://auth.example.com", "sub": "", "aud": "CLIENT_ID", "exp": 123456, "scope": "foo bar" } ~~~ The fields are as follows: * `sub` is empty, because this interaction involves not end-user or subject * `iss` is the configured URL from the Yuck configuration * `aud` is the requesting client id * `exp` is the timestamp in Unix seconds when the token stops being valid * `scope` is the space-delimieted list of scopes the client is allowed to have and has requested ### Get token with OAuth using client credentials ~~~scenario given a running Yuck at https://auth.example.com and I am a registered OAuth client and I am authorized to have scope foo when I do POST /token with grant_type=client_credential&scope=foo+bar then response status code is 200 and response has header content-type: application/json and response body is a valid, signed JWT token and token sub is empty and token has iss: "https://auth.example.com" and token has aud set to my client id and token expires at least 60 seconds in the future and token has scope: "foo" ~~~ ### End-user interactive login This is a bit long. ~~~scenario given a running Yuck at https://auth.example.com and I am registered as tomjon with password hunter2 and I am authorized to have scope foo and the application is registered at https://app.example.com when I go to the application login page then response status code is 302 and response has header Location: https://auth.example.com/auth?response_type=code&scope=openid+foo&client_id=facade&state=RANDOM&redirect_uri=https://app.example.com/callback when I follow the redirect then response status code is 200 and response has header content-type: text/html and body HTML form has username and body HTML form has password when I do POST /auth with username=tomjon&password=wrong then response status code is 401 when I do POST /auth with username=tomjon&password=hunter2 then response status code is 302 and response has header Location: https://app.example.com/callback?... and I remember the Location URL code parameter when I follow the redirect when the application requests a token using remembered code then response status code is 200 and response has header content-type: application/json and body has field access_token and body has field token_type: Bearer and body has field expires_in and access_token is a valid, signed JWT and access_token has scope: "foo" and access_token has sub: "tomjon" ~~~ --- title: Yuck or OIDC bindings: - yuck.yaml classes: - json ...