summaryrefslogtreecommitdiff
path: root/yarns/200-client-creds.yarn
blob: 9eff22a53eddbc45de5b3ac39bb12c5aa9b3f233 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
OAuth2 client credentials grant
=============================================================================

See [RFC8252][] for a description of the client credentials grant.

[RFC8252]: https://tools.ietf.org/html/rfc8252

In the client credentials grant flow, the API client makes the
following request to the authentication server:

    EXAMPLE client credentials access token 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

Qvisqve checks the `grant_type` parameter, and extracts `USERPASS` to
get the client id and secret. It compares them against statically
created clients, which it reads from the filesystem.

    EXAMPLE Qvisqve configuration file in YAML
    config:
      issuer: https://qvisqve.example.com
      lifetime: 3600
      signing_key: |
        -----BEGIN RSA PRIVATE KEY-----
        MIIJKAIBAAKCAgEAwWfX31I6DECIPv8FCffNKCF2BaxbPaSFQR+V+mEQxaxZmlmS
        ... deleted from example
        LkLFQC7Y66OYjna457hU545hfF99j7nxdseXQEhV96E4RUIub+6vS8TYDEk=
        -----END RSA PRIVATE KEY-----
      store: /var/lib/qvisqve

Each client will be stored as a separate YAML file under the directory
configured in the "store" configuration variable. For example, the
client `test_api` is stored in `/var/lib/qvisqve/clients/test_api`:

    EXAMPLE
    client_secret:
        N: 16384
        hash: 5cf3b9cab1eacc818b73d229db...a023e938ee598f6c49749ef0429a889f7
        key_len: 128
        p: 1
        r: 8
        salt: 18112c4c50993ca5db908a15519c51e1
        version: 1
    allowed_scopes:
    - foo
    - bar

Qvisqve checks that the client id given by the client is found, and
that the offered client secret matches what's in the configuration
file for the client id. It also takes the list of requested scopes,
and drops any requested scopes that are not in the list of allowed
scopes (in the example, it drops `foobar`).

If all these checks pass, Qvisqve will create a JWT with the following
claims:

    EXAMPLE sample access token claims
    {
        "iss": "https://qvisqve.example.com",
        "sub": "",
        "aud": "test-api",
        "exp": 123456,
        "scope": "foo bar"
    }

Note that there is no end user involved in the client-credentials
flow, and so that `sub` field is always the empty string. The `iss`
field comes from the configuration, `aud` is the client id in the
request, `exp` is current time plus the lifetime specified in the
configuration. `scope` is from the request filtered by the allowed
scopes, as described above.

    SCENARIO get token using client credentials

    GIVEN an API client "bigco"
    AND API client has secret "secrit"
    AND API client has allowed scopes "read write"

    AND a Qvisqve configuration for "https://qvisqve.example.com"
    AND Qvisqve configuration has a token lifetime of 3600
    AND a running Qvisqve instance

    WHEN client requests POST /token 
    ... with client_id "bigco", client_secret "secrit", and 
    ... scopes "read write delete"

    THEN HTTP status code is 200 OK
    AND Content-Type is application/json
    AND body is a correctly signed JWT token
    AND token has claim iss as "https://qvisqve.example.com"
    AND token has claim sub as ""
    AND token has claim aud as "bigco"
    AND token has claim scope as "read write"
    AND token expires in an hour

    FINALLY Qvisqve is stopped