2023-11-17 15:29:44 +01:00
|
|
|
# Authentication
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
Authentication in this project works in three steps:
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
1. first you [login](#login) and retrieve a [refresh token](#refresh-token)
|
|
|
|
2. send your [refresh token](#refresh-token) and retrieve a short-lived
|
|
|
|
[access token](#access-token)
|
|
|
|
3. send your [access token](#access-token) with every api request to access
|
|
|
|
protected recourses
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
See the [example](#example) for the most basic authentication flow. Note that
|
|
|
|
the script requests a new refresh-token every time, which should be avoided.
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
## Login
|
|
|
|
|
|
|
|
Make a request to `/auth/login` with your user credentials (username/user Id &
|
|
|
|
password) as json inside the `Authorization` http header field:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
Authorization: {"userId":"<userId>","password":"<password>"}
|
2023-11-05 17:42:14 +01:00
|
|
|
```
|
2023-11-17 15:29:44 +01:00
|
|
|
|
|
|
|
The order of the `json` fields does not matter. Note the use of `"` for valid
|
|
|
|
`json`.
|
|
|
|
|
|
|
|
### Usage
|
|
|
|
|
|
|
|
If your credentials are valid, you will get a [refresh token](#refresh-token) as
|
|
|
|
a response. Store it somewhere save and use it to request new
|
|
|
|
[access tokens](#access-token).
|
|
|
|
|
|
|
|
### Fields
|
|
|
|
|
|
|
|
Password and either Username or userId are required.
|
|
|
|
|
|
|
|
#### Password
|
|
|
|
|
|
|
|
Your password must only contain letters and numbers.
|
|
|
|
|
|
|
|
#### Username
|
|
|
|
|
|
|
|
Your username must only contain letters and numbers.
|
|
|
|
|
|
|
|
#### User Id
|
|
|
|
|
|
|
|
Your userId.
|
|
|
|
|
|
|
|
## Refresh Token
|
|
|
|
|
|
|
|
A json object containing a selector, token and expiry date used to request
|
|
|
|
[access tokens](#access-token)
|
|
|
|
|
|
|
|
```json
|
|
|
|
{ "selector": "<string>", "token": "<string>", "expiryDate": "<int>" }
|
2023-11-05 17:42:14 +01:00
|
|
|
```
|
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
### Obtaining
|
|
|
|
|
|
|
|
See [login](#login).
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
### Usage
|
|
|
|
|
|
|
|
Used to [request an access token](#obtaining-1).
|
|
|
|
|
|
|
|
### Fields
|
|
|
|
|
|
|
|
All fields are required.
|
|
|
|
|
|
|
|
#### selector
|
|
|
|
|
|
|
|
The base64 encoded URL-save representation of a random `byte[9]` array used to
|
|
|
|
retrieved the associated token-hash from the database.
|
|
|
|
|
|
|
|
#### token
|
|
|
|
|
|
|
|
The base64 encoded URL-save representation of a `byte[33]` array used to compare
|
|
|
|
against a hash stored in the database.
|
|
|
|
|
|
|
|
#### expiryDate
|
|
|
|
|
|
|
|
An integer representing the UNIX timestamp at which the associated refresh token
|
|
|
|
becomes invalid.
|
|
|
|
|
|
|
|
Currently hardcoded to **10 days** inside
|
|
|
|
[/database/crypto_helpers.go](../../database/crypto_helpers.go):
|
|
|
|
`const refreshTokenLifetime = "+10 day"`.
|
|
|
|
|
|
|
|
## Access Token
|
|
|
|
|
|
|
|
A JWT (JSON Web Token, [learn more](https://jwt.io/)) consisting of three base64
|
|
|
|
encoded URL-save parts separated by dots `.`.
|
|
|
|
|
|
|
|
Decoded & formatted:
|
2023-11-05 17:42:14 +01:00
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
2023-11-17 15:29:44 +01:00
|
|
|
"alg": "HS256",
|
|
|
|
"typ": "JWT"
|
2023-11-05 17:42:14 +01:00
|
|
|
}
|
2023-11-17 15:29:44 +01:00
|
|
|
.
|
|
|
|
{
|
|
|
|
"userId": "<int>",
|
|
|
|
"isAdmin": <bool>,
|
|
|
|
"isUserCreator": <bool>,
|
|
|
|
"expiryDate": <int>
|
|
|
|
}
|
|
|
|
.
|
|
|
|
<signature>
|
2023-11-05 17:42:14 +01:00
|
|
|
```
|
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
### Obtaining
|
|
|
|
|
|
|
|
Make a request to `/auth` with a [refresh token](#refresh-token) inside the
|
|
|
|
`Authorization` http header:
|
|
|
|
|
|
|
|
```json
|
|
|
|
Authorization: Refresh {"selector":"<string>","token":"<string>","expiryDate":<int>}
|
|
|
|
```
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
```
|
|
|
|
<base64 encoded header>.<base64 encoded payload>.<base64 encoded signature>
|
|
|
|
```
|
|
|
|
|
|
|
|
### Usage
|
|
|
|
|
|
|
|
When included in the `Authorization` http header, it gives you access based on
|
|
|
|
your user role.
|
|
|
|
|
|
|
|
```
|
|
|
|
Authorization: Bearer <base64 encoded header>.<base64 encoded payload>.<base64 encoded signature>
|
|
|
|
```
|
|
|
|
|
|
|
|
### Fields
|
|
|
|
|
|
|
|
All fields are required.
|
|
|
|
|
|
|
|
#### Header
|
|
|
|
|
|
|
|
##### alg
|
|
|
|
|
|
|
|
The algorithm used to create the signature. Currently only `HS256` is supported.
|
|
|
|
|
|
|
|
##### type
|
|
|
|
|
|
|
|
The **IANA Media Type**
|
|
|
|
([learn more](https://www.iana.org/assignments/media-types/media-types.xhtml))
|
|
|
|
of the payload. This field is always set to `"jwt"`.
|
|
|
|
|
|
|
|
#### Payload
|
|
|
|
|
|
|
|
##### userId
|
|
|
|
|
|
|
|
The userId associated with the access token.
|
|
|
|
|
|
|
|
##### isAdmin
|
|
|
|
|
|
|
|
A boolean indicating if the user has admin rights.
|
|
|
|
|
|
|
|
##### isUserCreator
|
|
|
|
|
|
|
|
A boolean indicating if the user has user creation rights.
|
|
|
|
|
|
|
|
##### expiryDate
|
|
|
|
|
|
|
|
An integer representing the UNIX timestamp at which the associated refresh token
|
|
|
|
becomes invalid.
|
|
|
|
|
|
|
|
Currently hardcoded to **10 minutes** inside
|
|
|
|
[database/crypto_helpers.go](../../database/crypto_helpers.go):
|
|
|
|
`const accessTokenLifetime = time.Minute * 10`.
|
|
|
|
|
|
|
|
#### Signature
|
|
|
|
|
|
|
|
A `SHA256` hash of the encoded header and payload. The value of
|
|
|
|
`database.secret` from the [config file](../../.YetAnotherToDoList.yaml) is used
|
|
|
|
as a key (salt).
|
|
|
|
|
|
|
|
## Why
|
|
|
|
|
|
|
|
We use access tokens with a lifespan of 10 min and an in-memory db to blacklist
|
|
|
|
revoked tokens. So if for e.g. a user changes it's password, we would add the
|
|
|
|
userId and the time of the change to the blacklist, invalidating all tokens of
|
|
|
|
that user that have been issued before.
|
2023-11-05 17:42:14 +01:00
|
|
|
|
|
|
|
After a server restart, all tokens will become invalid as well, since we can not
|
2023-11-17 15:29:44 +01:00
|
|
|
be sure which ones were revoked (this could be mitigated in the future by making
|
|
|
|
the blacklist persist during restarts).
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
If a token has expired (either by a server restart or after 10 min), a new token
|
|
|
|
is requested with a [refresh token](#refresh-token) that is stored in a
|
|
|
|
database.
|
2023-11-05 17:42:14 +01:00
|
|
|
|
|
|
|
### Pros:
|
|
|
|
|
|
|
|
- less DB lookups in general
|
|
|
|
|
|
|
|
### Cons:
|
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
- increased load on database after server restart since all active clients need
|
|
|
|
a new access token.
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
## Example
|
2023-11-05 17:42:14 +01:00
|
|
|
|
|
|
|
```bash
|
2023-11-17 15:29:44 +01:00
|
|
|
#!/bin/env bash
|
|
|
|
USERNAME="admin"
|
|
|
|
PASSWORD="temporaryPassword"
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
# login (you can replace `userName` with `userId` since the username might change)
|
|
|
|
REFRESH_TOKEN=$(curl -ksH "Authorization: Login {\"userName\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" https://localhost:4241/auth/login)
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
# request access token (every 10 minutes or after server restart)
|
|
|
|
ACCESS_TOKEN=$(curl -ksH "Authorization: Refresh $REFRESH_TOKEN" https://localhost:4241/auth)
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
# a request to a fictional protected resource
|
|
|
|
QUERY_RESULT=$(curl -ksH "Authorization: Bearer $ACCESS_TOKEN" https://localhost:4241/protected)
|
2023-11-05 17:42:14 +01:00
|
|
|
|
2023-11-17 15:29:44 +01:00
|
|
|
# "convert" the header & access token to a json string to use in client applications (e.g. GraphiQL)
|
|
|
|
echo -e "Use this as header for e.g. GraphiQL:\n{\"Authorization\":\"Bearer $ACCESS_TOKEN\"}"
|
|
|
|
```
|