OIDC support #380

Open
opened 2026-07-23 21:18:17 +00:00 by jsheunis · 1 comment
Owner

From @mih

https://forgejo.org/docs/latest/user/oauth2-provider/#public-client-pkce describes what is needed

  • shacl-vue performs that dance, and receives a (short-lived) bearer-token
  • shacl-vue passes that token on to dump-things, which should be able to function properly without modification
  • shacl-vue should retain the functionality to specify an explicit token, but should be configurable to use OIDC, and we should configure that by default, whenever a dumpthings uses forgejo as an authentication source. Importantly, this cannot be an all-or-nothing configuration. For one and the same site, a curator might have a static magic token, while other users use OIDC
From @mih > https://forgejo.org/docs/latest/user/oauth2-provider/#public-client-pkce describes what is needed > - shacl-vue performs that dance, and receives a (short-lived) bearer-token > - shacl-vue passes that token on to dump-things, which should be able to function properly without modification > - shacl-vue should retain the functionality to specify an explicit token, but should be configurable to use OIDC, and we should configure that by default, whenever a dumpthings uses forgejo as an authentication source. Importantly, this cannot be an all-or-nothing configuration. For one and the same site, a curator might have a static magic token, while other users use OIDC
Author
Owner

Some notes on design

This is the authorization URL template for a PKCE request to the forgejo OIDC server:

https://[YOUR-FORGEJO-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&code_challenge_method=CODE_CHALLENGE_METHOD&code_challenge=CODE_CHALLENGE&state=STATE

We need:

  • YOUR-FORGEJO-URL: e.g. https://hub.psychoinformatics.de (and then the app can add /login/oauth/authorize? to it automatically)
  • CLIENT_ID: this is the client id of the OAuth2 application created via settings in when logged into the forgejo instance. This act of creating the application also provides a client secret, but the whole point of using PKCE is to not have to use the secret in the browser.
  • REDIRECT_URI: e.g. https://pool.psychoinformatics.de/ui/oidc-callback; any of the redirect URLs specified when creating the OAuth2 application above (new ones can be added afterwards too); this is the url that the OIDC server redirects back to once it has received the code challenge for the given client
  • CODE_CHALLENGE_METHOD: we will use S256 because the web app can indeed compute a "URL-safe base64-encoded string of the SHA256 hash of code_verifier"
  • CODE_CHALLENGE: this is a "URL-safe base64-encoded string of the SHA256 hash of code_verifier", which in turn "has to be a random string with a minimum length of 43 characters and a maximum length of 128 character..."
  • STATE: "The state parameter is optional, but should be used to prevent CSRF attacks"

Steps in the OIDC workflow:

  1. app has a "Login via Psychinformatics Hub" button
  2. user hits the button, this calls a loginViaOIDC function
  3. loginViaOIDC function:
    • grabs YOUR-FORGEJO-URL, CLIENT_ID, REDIRECT_URI, and CODE_CHALLENGE_METHOD from app configuration
    • calculates CODE_CHALLENGE based on CODE_CHALLENGE_METHOD;
      • if S256 it will generate a random string, then get its SHA-256 digest, then base64 encode it
      • if plain it will generate a random string
    • generates STATE as random string
    • constructs auth url and opens it in a new tab
    • (needs to temporarily store the CODE-VERIFIER, i.e. the random string, and STATE; both are needed when the callback is handled in order to generate the token)
  4. The callback comes to the specified redirect URL in the format: https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE. Now there needs to be a callback handler that should:
    • grab the stored STATE and compare with the returned STATE; if not the same, fail the CSRF check
    • grab the RETURNED_CODE
    • use the code and stored CODE-VERIFIER in the POST to the forgejo /login/oauth/access_token endpoint in order to exchange the code for a token. it needs:
      • client_id: from config
      • code: the returned code
      • grant_type: "authorization_code" (from forgejo docs)
      • redirect_uri: from config
      • code_verifier: grabbed from temporary storage
    • the returned token can then be stored in the app session storage, along with other tokesn already stored in that way

Notes:

  • shacl-vue will need to support updated configuration to allow configuring a complete OIDC workflow
  • shacl-vue will need additional functionality to allow multiple token entry, one of which could be via the OIDC workflow, and the option to select the "currently active" token to be used in ongoing submissions and data fetching
  • shacl-vue has a large startup cost (loading config, schemas, data), and we don't want the redirect url to retrigger that -> the app state should be unaffected by the OIDC workflow. We could introduce app routing (which is currently used very minimally in the app), and create a specific Vue component that renders when the /oidc-callback path is accessed, and the app should then be updated to know that startup steps should be ignored when accessing that specific path/route. But if the navigation to the OIDC server is initially done in the same browser tab, we would also lose app state. This is why I think using the new tab/popup option is better.
  • Assuming the popup route, and to keep things simple on the Vue side, we could:
    • have a separate basic html/JS page located at the redirect URL and which will have the necessary code to handle the callback and code exchange
    • once the token is retrieved by the callback handler, it can share this via window.opener.postMessage
    • once finished, the popup page can close via window.close, this will show the original app page again, now with the token available.
## Some notes on design This is the authorization URL template for a PKCE request to the forgejo OIDC server: ``` https://[YOUR-FORGEJO-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&code_challenge_method=CODE_CHALLENGE_METHOD&code_challenge=CODE_CHALLENGE&state=STATE ``` We need: - `YOUR-FORGEJO-URL`: e.g. `https://hub.psychoinformatics.de` (and then the app can add `/login/oauth/authorize?` to it automatically) - `CLIENT_ID`: this is the client id of the OAuth2 application created via settings in when logged into the forgejo instance. This act of creating the application also provides a client secret, but the whole point of using PKCE is to not have to use the secret in the browser. - `REDIRECT_URI`: e.g. `https://pool.psychoinformatics.de/ui/oidc-callback`; any of the redirect URLs specified when creating the OAuth2 application above (new ones can be added afterwards too); this is the url that the OIDC server redirects back to once it has received the code challenge for the given client - `CODE_CHALLENGE_METHOD`: we will use `S256` because the web app can indeed compute a "URL-safe base64-encoded string of the SHA256 hash of code_verifier" - `CODE_CHALLENGE`: this is a "URL-safe base64-encoded string of the SHA256 hash of code_verifier", which in turn "has to be a random string with a minimum length of 43 characters and a maximum length of 128 character..." - `STATE`: "The state parameter is optional, but should be used to prevent CSRF attacks" Steps in the OIDC workflow: 1. app has a "Login via Psychinformatics Hub" button 2. user hits the button, this calls a `loginViaOIDC` function 3. `loginViaOIDC` function: - grabs `YOUR-FORGEJO-URL`, `CLIENT_ID`, `REDIRECT_URI`, and `CODE_CHALLENGE_METHOD` from app configuration - calculates `CODE_CHALLENGE` based on `CODE_CHALLENGE_METHOD`; - if `S256` it will generate a random string, then get its SHA-256 digest, then base64 encode it - if `plain` it will generate a random string - generates `STATE` as random string - constructs auth url and opens it in a new tab - (needs to temporarily store the `CODE-VERIFIER`, i.e. the random string, and `STATE`; both are needed when the callback is handled in order to generate the token) 4. The callback comes to the specified redirect URL in the format: `https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE`. Now there needs to be a callback handler that should: - grab the stored `STATE` and compare with the returned `STATE`; if not the same, fail the CSRF check - grab the `RETURNED_CODE` - use the code and stored `CODE-VERIFIER` in the POST to the forgejo `/login/oauth/access_token` endpoint in order to exchange the code for a token. it needs: - `client_id`: from config - `code`: the returned code - `grant_type`: "authorization_code" (from forgejo docs) - `redirect_uri`: from config - `code_verifier`: grabbed from temporary storage - the returned token can then be stored in the app session storage, along with other tokesn already stored in that way Notes: - `shacl-vue` will need to support updated configuration to allow configuring a complete OIDC workflow - `shacl-vue` will need additional functionality to allow multiple token entry, one of which could be via the OIDC workflow, and the option to select the "currently active" token to be used in ongoing submissions and data fetching - `shacl-vue` has a large startup cost (loading config, schemas, data), and we don't want the redirect url to retrigger that -> the app state should be unaffected by the OIDC workflow. We could introduce app routing (which is currently used very minimally in the app), and create a specific Vue component that renders when the `/oidc-callback` path is accessed, and the app should then be updated to know that startup steps should be ignored when accessing that specific path/route. But if the navigation to the OIDC server is initially done in the same browser tab, we would also lose app state. This is why I think using the new tab/popup option is better. - Assuming the popup route, and to keep things simple on the Vue side, we could: - have a separate basic html/JS page located at the redirect URL and which will have the necessary code to handle the callback and code exchange - once the token is retrieved by the callback handler, it can share this via `window.opener.postMessage` - once finished, the popup page can close via `window.close`, this will show the original app page again, now with the token available.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
orinoco/shacl-vue#380
No description provided.