Description
Once #154 has merged we still have some features to improve authentication. We still check the database for every single authentication request, but we can remove that with JSON Web Tokens (JWTs).
Tasks
- Adding a
JWT_SECRET key to our web server.
- Modifying our
user_session table.
- Changing our hardened cookie (access token) to be a JWT that contains enough information to not require a database read.
- Access token will no longer be a session cookie and will expire after a long-ish time.
- Add a second cookie (refresh token), which expires after a short time.
- Add a third cookie, which is plaintext, expires after the session, and contains information about the user.
- Update
auth endpoints.
- Update dependencies on other endpoints.
User Session table updates
- Adding a
scopes column to our user_session table. This means we don't need to check the officers table for who can do what every time.
scopes will be an array of text (TEXT[]) in Postgres.
- A scope will consist of access type and domain.
- Access type is either
read or write. Having one level does not imply having the other i.e. even if you can write that doesn't mean you can read
- Domains will be
admin, executive, and elections.
- E.g.
write:admin allows a user to write to admin-level objects, but not read them.
scopes are created when the user is logs in and are only applied for the session.
- Add an
expires_at column, which is a datetimetz of when the token expires.
- Add an
is_revoked column, which is a boolean that is true when access is revoked or the token has expired, false otherwise
- Change the primary key from the
computing_id to the session_id.
Access Token
- Stateless JWT that contains:
iss: domain name of the web server (https://sfucsss.org atm).
sub: computing ID of the user.
iat: UNIX epoch time in seconds when the token was issued.
exp: UNIX epoch time in seconds when the token expires.
scopes: string array of scopes.
sid: session ID, which is the ID of the refresh token that generated this access token.
- Whenever a request that requires authorization is accessed, this should be sent by the frontend.
- The backend will verify this checking the signature (using HS256) and the expiry.
- If the token has expired, send status 401.
- If the token has not expired then trust the contents of it for GET requests.
- POST, PUT, and DELETE all require verification of the
sid and ensure it's not revoked.
- Expires after 15 minutes.
Example
{
"iss": "https://sfucsss.org",
"sub": "compid12"
"iat": 1234567890,
"exp": 1234568790
"sid": "hkljlkljsdlkjffyekd"
}
Cookie properties:
- HttpOnly=True
- Secure=True
- SameSite="Lax"
- Domain=".sfucsss.org"
Refresh Token
- Basically our current session token, except with an expiration time.
- The hashed value of this (SHA265) is stored as the primary key in the
user_session database.
- We don't store the raw value at all.
Cookie properties:
- HttpOnly=True
- Secure=True
- SameSite="Lax"
- Domain=".sfucsss.org"
- Path="/auth/refresh": this indicates this token is only sent on the
/auth/refresh endpoint.
Session Token
- Contains a JSON object consisting of the user's a CSRF token,
computing_id and an array of scopes.
- The CSRF token must be sent on every POST, PUT, and DELETE requests in a
X-CSRF-Token header
- Expires after 15 minutes.
Cookie properties:
- HttpOnly=False: it needs to be accessible by a frontend
- Secure=True
- SameSite="Lax"
- Domain=".sfucsss.org"
auth Endpoint Updates
login: Creates the three tokens, only the refresh token is stored in our user_session table. If the user already has refresh token in the database then revoke them.
logout: Reads the refresh token (even if expired), sets it to revoked and sends a response to the browser to delete all three cookies.
refresh: Gets the refresh token from the user, verifies it has not been revoked or expired, and creates a new access token. Revokes any older access tokens. If the token
user: Remove this endpoint
Dependency updates
- Instead of hitting the database, the user's access token will be read for their permissions.
- If the access token is revoked, immediately send a 401, except for
/auth/refresh and /auth/logout.
Description
Once #154 has merged we still have some features to improve authentication. We still check the database for every single authentication request, but we can remove that with JSON Web Tokens (JWTs).
Tasks
JWT_SECRETkey to our web server.user_sessiontable.authendpoints.User Session table updates
scopescolumn to ouruser_sessiontable. This means we don't need to check theofficerstable for who can do what every time.scopeswill be an array of text (TEXT[]) in Postgres.readorwrite. Having one level does not imply having the other i.e. even if you can write that doesn't mean you can readadmin,executive, andelections.write:adminallows a user to write to admin-level objects, but not read them.scopesare created when the user is logs in and are only applied for the session.expires_atcolumn, which is adatetimetzof when the token expires.is_revokedcolumn, which is abooleanthat is true when access is revoked or the token has expired, false otherwisecomputing_idto thesession_id.Access Token
iss: domain name of the web server (https://sfucsss.orgatm).sub: computing ID of the user.iat: UNIX epoch time in seconds when the token was issued.exp: UNIX epoch time in seconds when the token expires.scopes: string array of scopes.sid: session ID, which is the ID of the refresh token that generated this access token.sidand ensure it's not revoked.Example
{ "iss": "https://sfucsss.org", "sub": "compid12" "iat": 1234567890, "exp": 1234568790 "sid": "hkljlkljsdlkjffyekd" }Cookie properties:
Refresh Token
user_sessiondatabase.Cookie properties:
/auth/refreshendpoint.Session Token
computing_idand an array ofscopes.X-CSRF-TokenheaderCookie properties:
authEndpoint Updateslogin: Creates the three tokens, only the refresh token is stored in ouruser_sessiontable. If the user already has refresh token in the database then revoke them.logout: Reads the refresh token (even if expired), sets it to revoked and sends a response to the browser to delete all three cookies.refresh: Gets the refresh token from the user, verifies it has not been revoked or expired, and creates a new access token. Revokes any older access tokens. If the tokenuser: Remove this endpointDependency updates
/auth/refreshand/auth/logout.