|
| 1 | +--- |
| 2 | +title: "Token Authentication" |
| 3 | +description: "" |
| 4 | +summary: "" |
| 5 | +date: 2024-08-14T13:52:09+03:00 |
| 6 | +lastmod: 2024-08-14T13:52:09+03:00 |
| 7 | +draft: false |
| 8 | +weight: 215 |
| 9 | +toc: true |
| 10 | +seo: |
| 11 | + title: "" # custom title (optional) |
| 12 | + description: "" # custom description (recommended) |
| 13 | + canonical: "" # custom canonical URL (optional) |
| 14 | + noindex: false # false (default) or true |
| 15 | +--- |
| 16 | + |
| 17 | +Token authentication enhances security by allowing access restrictions and an expiration to be specified when generating the token credentials. |
| 18 | +If the token is exposed to an attacker, they would only have limited access to the resources and the token would become invalid after it expires. |
| 19 | + |
| 20 | +The token is secured by a secret key [configured](#securing--encrypting-the-token) on the REST server. |
| 21 | + |
| 22 | +A token can be created by authenticating with a valid `username/password` combination (Basic Authentication) or by authenticating with an existing token against the `/login` resource. |
| 23 | +To minimize security risks, the request body should contain appropriate permissions and expiration times. |
| 24 | +The server would respond with a `token` which can be used for subsequent calls to the REST services. |
| 25 | + |
| 26 | +For basic authentication requests, the requested permissions cannot exceed the authenticated user. |
| 27 | +When logging in via a token, the expiration time and permissions cannot exceed the token used for authentication. |
| 28 | + |
| 29 | +## Token creation (authentication) |
| 30 | + |
| 31 | +A token is created and returned by sending a `POST /login` request and providing user credentials (i.e. username/password). |
| 32 | + |
| 33 | +Here is a simplified example of creating authentication token: |
| 34 | + |
| 35 | +```text |
| 36 | +POST /login |
| 37 | +Authorization: Basic YnN... |
| 38 | +{...} |
| 39 | +
|
| 40 | +HTTP/1.1 200 OK |
| 41 | +{"token":"eyJ6a..."} |
| 42 | +``` |
| 43 | + |
| 44 | +## Using the Token to access resources |
| 45 | + |
| 46 | +The value of the `token` attribute returned from the `/login` resource is passed into the `Authorization` header using the `Bearer` authorization scheme. |
| 47 | + |
| 48 | +For example: |
| 49 | + |
| 50 | +```text |
| 51 | +GET /users/acme/orgadmin |
| 52 | +Authorization: Bearer eyJ6a.... |
| 53 | +
|
| 54 | +HTTP/1.1 OK |
| 55 | +{"organization":"acme","name"......} |
| 56 | +``` |
| 57 | + |
| 58 | +## Token Expiration |
| 59 | + |
| 60 | +The token expiration can be set with the `expiresIn` or `expiresAtTime` parameter. |
| 61 | +If both are specified, `expiresAtTime` takes precedence. |
| 62 | +If none are specified, it will set to the default of 2 hours. |
| 63 | + |
| 64 | +- `expiresIn` needs to be a number followed by either "s", "m" or "h" or a combination of them. It will create a token which will expire in the specified number of seconds, minutes or hours from now |
| 65 | +- `expiresAtTime` specifies the time when the token should expire. It must be in ISO-8601 format, i.e. `YYYY-MM-DDTHH:MM:SSZ` |
| 66 | + |
| 67 | +{{< callout context="tip" title="Tip" icon="outline/brand-hipchat" >}} |
| 68 | +The expiration timestamp should be chosen carefully. |
| 69 | +If it is set too far into the future, an exposure of the token to an attacker could give them access to the resource for an extended period of time. |
| 70 | +If the expiration is set too small, it would require the user to provide their credentials more often. |
| 71 | +{{< /callout >}} |
| 72 | + |
| 73 | +For example, supply `expiresIn` to limit the token expiration: |
| 74 | + |
| 75 | +```text |
| 76 | +POST /login |
| 77 | +Authorization: Basic YWN..... |
| 78 | +{"expiresIn":"3h"} |
| 79 | +
|
| 80 | +HTTP/1.1 200 OK |
| 81 | +{"token":"eyJ6a...."} |
| 82 | +``` |
| 83 | + |
| 84 | +For example, use default token expiration settings: |
| 85 | + |
| 86 | +```text |
| 87 | +POST /login |
| 88 | +Authorization: Basic YWN..... |
| 89 | +{"expiresAtTime":"2025-05-22T16:00:00Z"} |
| 90 | +
|
| 91 | +HTTP/1.1 200 OK |
| 92 | +{"token":"eyJ6a...."} |
| 93 | +``` |
| 94 | + |
| 95 | +## Authorization (Access Rules) |
| 96 | + |
| 97 | +The user's access rules are controlled by [Access Rule Entries]({{< ref "user-access-rules.md" >}}). |
| 98 | + |
| 99 | +- if `limitAllow` rules are specified in the request, it will replace the existing "allow" rules for the user. If the user requests more permission than they (or the token during re-issue) has, the request will be rejected. |
| 100 | +- if a `extraDeny` rule is specified, it will further restrict the user's permissions (it will be added to the user's deny rules, not replaced) |
| 101 | +- The response fill return the resulting access levels for the user (a combination of the current user's (or token's) access rules with the requested ones) |
| 102 | + |
| 103 | +For example: |
| 104 | + |
| 105 | +```text |
| 106 | +POST /login |
| 107 | +Authorization: Basic YWN..... |
| 108 | +{"limitAllow":["all:acme","read:corp"],"extraDeny":["delete:acme"]} |
| 109 | +
|
| 110 | +HTTP/1.1 200 OK |
| 111 | +{"token":"eyJ6a....", "accessRule":{"allow":["all:acme","raed:corp"], "deny":["delete:corp","delete:acme"]}} |
| 112 | +``` |
| 113 | + |
| 114 | +## Re-Issuing a token |
| 115 | + |
| 116 | +Re-issuing a token with an existing token is permitted, but for security reasons restricted: |
| 117 | + |
| 118 | +- a re-issue of the token cannot exceed the existing expiration time |
| 119 | +- specified access rules cannot exceed the existing access rules for the current token |
| 120 | + |
| 121 | +There are several scenarios where re-issuing a token is useful: |
| 122 | + |
| 123 | +- a token with a long expiration (stored in a very secure place like a security vault) can be created which can be used to re-issue a short lived token which could be shared with a temporary job. |
| 124 | +- a token with higher permissions (access rules) can be re-issued with less access. For example a background process might need only read permissions. |
| 125 | + |
| 126 | +For example: |
| 127 | + |
| 128 | +```text |
| 129 | +POST /login |
| 130 | +Authorization: Bearer eyJ6..... |
| 131 | +{"limitAllow":["all:acme","read:corp"],"extraDeny":["delete:acme"]} |
| 132 | +
|
| 133 | +HTTP/1.1 200 OK |
| 134 | +{"token":"abc...."} |
| 135 | +``` |
| 136 | + |
| 137 | +## Token payload |
| 138 | + |
| 139 | +Once a user performs a login (via POST /login), the response will have a "token" attribute containing a [JWT token](https://datatracker.ietf.org/doc/html/rfc7519). For security reasons this token is encrypted. |
| 140 | + |
| 141 | +## Create and read a token |
| 142 | + |
| 143 | +If the `acme/orgadmin` user doesn't exist yet, [create]({{< ref "user-management.md" >}}#creating-users) it. |
| 144 | + |
| 145 | +{{< tabs "token" >}} |
| 146 | +{{< tab "nuodb-cp" >}} |
| 147 | + |
| 148 | +```sh |
| 149 | +nuodb-cp httpclient POST login --user acme/orgadmin --password secret \ |
| 150 | + -d '{"extraDeny": ["write:*", "delete:*"]}' --jsonpath token --unquote |
| 151 | +``` |
| 152 | + |
| 153 | +```json |
| 154 | +{"token":"eyJ6aXAiOi...","accessRule": {"allow": ["all:acme"], "deny":"extraDeny":["write:*", "delete:*"]},"expiresAtTime":"2024-02-20T17:41:00Z"} |
| 155 | +``` |
| 156 | + |
| 157 | +The returned token can be used by subsequent `nuodb-cp` commands by either setting the `NUODB_CP_TOKEN` environment variable or passing it to the `--token` argument. |
| 158 | + |
| 159 | +{{< /tab >}} |
| 160 | +{{< tab "curl" >}} |
| 161 | + |
| 162 | +Set the environment variable `NUODB_CP_URL_BASE` to the REST service location, e.g. `http://server:8080` |
| 163 | + |
| 164 | +```sh |
| 165 | +curl -X POST -H "Content-Type: application/json" $NUODB_CP_URL_BASE/login -u acme/orgadmin:passw0rd \ |
| 166 | + -d '{"limitAllow":["all:acme"]}' |
| 167 | +``` |
| 168 | + |
| 169 | +```json |
| 170 | +{"token":"eyJ6aXAiOi...","accessRule": {"allow": ["all:acme"], "deny":"extraDeny":[]},"expiresAtTime":"2024-02-20T17:41:00Z"} |
| 171 | +``` |
| 172 | + |
| 173 | +Authenticate the user with the token: |
| 174 | + |
| 175 | +```sh |
| 176 | +curl $NUODB_CP_URL_BASE/users/acme -H "Authorization: Bearer eyJ6aXAiOi...." |
| 177 | +``` |
| 178 | + |
| 179 | +```json |
| 180 | +{"items":["orgadmin"]} |
| 181 | +``` |
| 182 | + |
| 183 | +{{< /tab >}} |
| 184 | +{{< /tabs >}} |
| 185 | + |
| 186 | +### Re-Issuing tokens with reduced permissions |
| 187 | + |
| 188 | +Especially for short running batch processes, it is often useful, to give a short lived token with minimal permissions to reduce a security exposure. |
| 189 | +For example a parent process having a token with a long lived expiration date, can request a short lived token from the server and pass it to a child process to minimize security exposure. |
| 190 | + |
| 191 | +{{< tabs "login" >}} |
| 192 | +{{< tab "nuodb-cp" >}} |
| 193 | + |
| 194 | +```sh |
| 195 | +export NUODB_CP_TOKEN="$(nuodb-cp httpclient POST login \ |
| 196 | + --token "eyJ6aXAi0i..." \ |
| 197 | + -d '{"limitAllow": ["read:acme"]}' --jsonpath token --unquote)" |
| 198 | +nuodb-cp database list acme |
| 199 | +``` |
| 200 | + |
| 201 | +{{< /tab >}} |
| 202 | +{{< tab "curl" >}} |
| 203 | + |
| 204 | +```sh |
| 205 | +curl -X POST -H "Content-Type: application/json" $NUODB_CP_URL_BASE/login -H "Authorization: Bearer eyJ6aXAiOi..." \ |
| 206 | + -d '{"limitAllow":["read:acme"]}' |
| 207 | +``` |
| 208 | + |
| 209 | +```json |
| 210 | +{"token":"abc..."} |
| 211 | +``` |
| 212 | + |
| 213 | +{{< /tab >}} |
| 214 | +{{< /tabs >}} |
| 215 | + |
| 216 | +## Appendix |
| 217 | + |
| 218 | +### Securing / Encrypting the token |
| 219 | + |
| 220 | +The token is secured by either a secret key or a cryptographically secure (and strong) password. |
| 221 | +A Kubernetes secret will store this information and helm charts will create this key automatically if it doesn't exist. |
| 222 | +Changing the key or password will invalidate all active tokens. |
| 223 | + |
| 224 | +```yaml |
| 225 | +apiVersion: v1 |
| 226 | +kind: Secret |
| 227 | +metadata: |
| 228 | + name: nuodb-cp-runtime-config |
| 229 | + namespace: nuodb |
| 230 | +type: Opaque |
| 231 | +data: |
| 232 | + secretPassword: ..... |
| 233 | +``` |
| 234 | +
|
| 235 | +- secretKey - binary key. If not set, the `secretPassword` needs to be specified. |
| 236 | +- secretPassword - password to convert into a key. Ignored if secretKey is specified. Please ensure it has sufficient strength. |
| 237 | +- secretKeyAlgorithm - algorithm of the secret key. Defaults to `HmacSHA256` if it is not set. |
| 238 | +- secretPasswordToKeyAlgorithm - algorithm to convert a password into a key. Defaults to `PBKDF2WithHmacSHA256` if it is not set. |
| 239 | +- secretPasswordToKeyIterations - key generation iterations if `secretPassword` is set. Defaults to `65536` if set to a non-positive value. |
| 240 | +- secretPasswordToKeyLength - key length if `secretPassword` is set. Defaults to `256` if set to a non-positive value. |
| 241 | + |
| 242 | +#### Create a secret password key in Kubernetes |
| 243 | + |
| 244 | +```sh |
| 245 | +kubectl create secret generic nuodb-cp-runtime-config --from-literal secretPassword=changeIt |
| 246 | +``` |
0 commit comments