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
|