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 API client tied to a subject ----------------------------------------------------------------------------- SCENARIO get token using client credentials tied to a subject GIVEN an API client "bigco" AND API client has secret "secrit" AND API client has allowed scopes "read write" AND API client has subject "tomjon" 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 "tomjon" 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