ecamp / ecamp/ecamp3

Implement Hitobito OAuth flow, Integrate Hitobito API

Open
#10,400 4 comments 0 reactions 1 assignee Claimed by @eliaSchenker View on GitHub
Ready for implementation type: API type: Frontend
Dominant language
PHP
Stars
156
Forks
72
Avg merge
12h 43m
Merged PRs (30d)
203

Description

To access Hitobito-related data, we need to retrieve the user's Hitbito access token using OAuth. This flow is intentionally kept separate from the authentication flow as we require additional scopes to call the api.

Upon accessing a feature that requires access to the Hitobito API, we always initiate the OAuth flow. The user grants permission to eCamp to access events on their behalf and is then redirected back to eCamp. The OAuth access token acquired during this process is stored in the cookies and sent to the backend for every subsequent request, which allows it to access Hitobito on behalf of the user.

This issue describes the backend/frontend changes necessary to implement this.

## Initiating OAuth Flow (frontend)

Whenever the user attempts to use a feature that requires access to the Hitobito API ("Import camp", "Sync camp", etc.), we initiate the OAuth flow:
1. Call `/api/hitobito//oauth?callback=`
- The user must always specify, which Hitobito provider they want to use (unless we already know it from the connected camp they are accessing)
- The frontend specifies the callback url (frontend path of the feature, such as `/camps/hitobito/pbsmidata/import`), where the user will be redirected after they complete the OAuth flow
2. User is redirected to Hitobito instance, authorizes eCamp with additional scopes
3. `/api/hitobito//oauth/callback` is called, which sets the cookies required by other endpoints later on, and redirects back to the frontend at the specified callback url
4. The frontend is now able to call endpoints requiring access to Hitobito

## Implement Hitobito OAuth endpoints (backend)

The following endpoints implement the OAuth flow used to authorize the user with Hitobito. The implementation is similar to the existing OAuth endpoints.

Both endpoints require that the user is authenticated.
### Start OAuth Flow
`GET /api/hitobito//oauth`
1. Check that the provider is allowed (currently only `pbsmidata`)
1. Otherwise return `404 Not Found`
2. Check that the `callback` query parameter is given and matches the allowlist:
- `/camps/hitobito//import`
- `/camps///hitobito/sync`
- `/camps///hitobito/invite`
- `/camps/hitobito//`
1. If allowlist does not match, respond with `400 Bad Request`
3. Redirect to Hitobito, initializing OAuth with the following scopes: `events event_participations people`
- Make sure to use the same library as with standard authentication, so that state signing / nonce are taken care of
- As the additional data, pass the provided `callback` and `eCampUserId` (so we can determine who initiated the request upon callback by Hitobito)

### OAuth Callback
`GET /api/hitobito//oauth/callback`
1. Check that the provider is allowed (currently only `pbsmidata`)
1. Otherwise return `404 Not Found`
2. Retrieve the OAuth access token and check if it is valid
- Make sure to use the same library as with standard authentication, so that state is verified correctly
- If the access token cannot be retrieved (invalid, or user rejection), redirect to the index page of the frontend
3. Encrypt the access token using an authenticated encryption algorithm (such as AES-GCM) and the `HITOBITO_TOKEN_ENCRYPTION_KEY` . Specify the user id as the AAD (Additional Authentictated Data), so that we can verify that an access token was created for a specific eCamp user.
4. Set `_hitobito__token` to the encrypted value
- Use `SameSite` to `Strict`, `Secure`/`HttpOnly` to `true` and the `Max-Age` to the expiry of the access token
5. Redirect to the callback URL

> Note: This endpoint should not require authentication. This is because the authentication cookies (`jwt_hp` and `jwt_s`) are both `SameSite=Strict` so we cannot read them when the user is redirected back.
>
> To gain access to the user's eCamp id (which is required later down the line), we store it in the `additional_data`, alongside the callback URL.

## Retrieving / Verifying access token (backend)
Once the frontend calls an endpoint that requires access to Hitobito, the backend must evaluate whether it can make the call:

1. Retrieve the cookies `_hitobito__token`
- If cookie is not set, respond with `403 Forbidden` and error `type` `/errors/hitobito-access-token-invalid`
- The provider used needs to be explicitly specified in endpoints that make calls to Hitobito
2. Retrieve the access token / user id by decrypting `_hitobito__token` using the `HITOBITO_TOKEN_ENCRYPTION_KEY`. Specify the user id as the AAD (Additional Authentictated Data). If a user tries to send an access token cookie that was created for a different account, the decryption will fail.
- If decryption fails, respond with `403 Forbidden` and error `type` `/errors/hitobito-access-token-invalid`

The target endpoint can now use the provider to determine the base URL and send the access token in the `Authorization` header. If a call to Hitobito fails for any unexpected reason (authentication fails, bad request, not reachable), respond with `500 Internal Server Error`

## Environment Variable Changes
The following environment variables are added:
- `HITOBITO_TOKEN_ENCRYPTION_KEY`
- 256 bits (32 bytes), base64 encoded
- Symmetric key used for encrypting/decrypting the hitobito access token when storing it in the cookie
- `HITOBITO_PBSMIDATA_API_BASE_URL`
- Base URL of the Hitobito API (MiData instance)
- `HITOBITO_API_OAUTH_SCOPES`
- Defines the scopes used when accessing the Hitobito API using OAuth

## Notes for Deployment
The eCamp OAuth Application (`eCamp v3`) must be updated at MiData (or any other providers in the future) to include
- the additional required scopes specified above
- the new redirect URis `/api/hitobito//oauth/callback` (one entry for each provider)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.