Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature request: hooks for managing oidc/oauth states #3938

Open
5 tasks done
aran opened this issue May 28, 2024 · 3 comments
Open
5 tasks done

feature request: hooks for managing oidc/oauth states #3938

aran opened this issue May 28, 2024 · 3 comments
Labels
feat New feature or request.

Comments

@aran
Copy link

aran commented May 28, 2024

Preflight checklist

Ory Network Project

No response

Describe your problem

OAuth/OIDC includes state that lives outside of Ory, but is affected by workflows under Ory's control. For example, a refresh token may be invalidated, or a user may grant or revoke various scopes. The application may detect the invalid refresh token, or the loss of some important scope. In this situation, the application will want to store state to indicate the details of this condition to be able to notify the user and direct them to a corrective action, as well as configure the corrective action. When the corrective action is taken, this state must be invalidated or updated.

Describe your ideal solution

It would be nice to Ory Kratos to offer support for managing various OAuth states such as:

  1. A dedicated place to store information about the (in)validity of a refresh token, or of a granted scope
  2. Built-in support for querying APIs like https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= to ensure scope validity
  3. Re-requesting scopes on flows like ...login?refresh=true
  4. Hooks for automatically invalidating the state in (1) on flows that touch it (login, settings)

Workarounds or alternatives

One way to achieve the above might be to:

  1. Add some identity metadata to each oidc-using schema to track the validity of a refresh token, scopes etc
  2. Update the identity metadata when an invalid refresh token or missing scope is detected
  3. Ask the user to go to settings, add a password, then disconnect their OIDC connection, then reconnect
  4. Clear the identity metadata by calling back to Kratos in a custom post-settings hook

Another way would be to use a separate OAuth state manager with its own storage for tokens, and only use Kratos for the initial connection but then after that use the other system and just try to keep the two systems in sync where necessary. This would end up duplicating a significant amount of configuration.

Version

Kratos 1.1

Additional Context

For case of a bad refresh token, I tried a PUT to overwrite the "initial_refresh_token" with an empty value to indicate that a reconnect is needed. But Kratos seems to reject updating that field.

@aran aran added the feat New feature or request. label May 28, 2024
@jonas-jonas
Copy link
Contributor

For case of a bad refresh token, I tried a PUT to overwrite the "initial_refresh_token" with an empty value to indicate that a reconnect is needed. But Kratos seems to reject updating that field.

AFAIK, Kratos only stores the initial access+refresh tokens and doesn't update them at all. They are just available to the implementor in case they want to request more data from the OIDC provider, just after completing the registration flow.

I guess we could store the new access + refresh tokens after logging again - not sure why this isn't the case right now.

When would you check for the scope validity? E.g. what would the API for this look like?

@aran
Copy link
Author

aran commented May 30, 2024

I guess we could store the new access + refresh tokens after logging again - not sure why this isn't the case right now.

One reason might be that the request issued to authorization server must be different - prompt=none (or omitted) vs. prompt=consent. Actually that raises that another way to look at this would be that functionally, it would be useful to have Kratos store info to better informwhether to set prompt=consent when Kratos talks to OAuth authorization server.

When would you check for the scope validity?

One way that is congruent with how other things in Kratos work would be to have a named hook (like the session hook), which if specified in configuration, would check the tokens after receiving them in a provider-dependent manner (e.g. for Google, https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=...)

E.g. what would the API for this look like?

Scope checking:

        oidc:
          hooks:
          - hook: "session"
          - hook: "scopes"

User storage, something like this (assuming scopes is omittable when unknown, and scopes, access_token and refresh_token can be GET/PUT over the HTTP admin API:

 {
     "id": "d50b35ad-6b22-4b0d-9f7e-2bb1ced8816e",
     "credentials": {
         "oidc": {
           "type": "oidc",
           "identifiers": [
             "google-web-1:123456789012345678901"
           ],
           "config": {
             "providers": [
               {
                 "initial_id_token": "w6MncQ1TfhzHpg",
                 "subject": "123456789012345678901",
                 "provider": "google-web-1",
                 "organization": "",
                 "initial_access_token": "+oUl+QpCe5Rpug",
                 "initial_refresh_token": "5L/84eMTDMXw4g",
+                "scopes": [
+                    "openid",
+                    "email",
+                    "profile",
+                    "offline_access",
+                    "https://www.googleapis.com/auth/userinfo.email"
+                ],
+                "scopes_changed_at": "2024-05-30T15:09:08.433369Z",
+                "access_token": "w/EhoZCq1ympjQ==",
+                "access_token_changed_at": "2024-05-30T15:09:08.433369Z",
+                "refresh_token": "QPoBK8rOB82TeA",
+                "refresh_token_changed_at": "2024-05-30T15:09:08.433369Z"
               }
             ]
           },
           "version": 0,
           "created_at": "2024-05-30T15:09:08.433369Z",
           "updated_at": "2024-05-30T15:09:08.433369Z"
         },
         "password": {
           "type": "password",
           "identifiers": [
             "+12025555555"
           ],
           "version": 0,
           "created_at": "2024-05-30T15:09:08.433365Z",
           "updated_at": "2024-05-30T15:09:08.433365Z"
         }
       },
     "schema_id": "default.0",
     "schema_url": "http://localhost:4433/schemas/ZGVmYXVsdC4w",
     "state": "active",
     "state_changed_at": "2024-05-30T15:05:52.766066Z",
     "traits": {
         "name": "Toby",
         "phone": "+12025555555"
     },
     "metadata_public": null,
     "metadata_admin": null,
     "created_at": "2024-05-30T15:05:52.766732Z",
     "updated_at": "2024-05-30T15:05:52.766732Z",
     "organization_id": null
 }

(Edited because I rediscovered upstream_parameters)

@aran
Copy link
Author

aran commented Jun 10, 2024

After learning more about this stuff, there's also an argument to be made that these tokens "should" be encrypted. Anyone who wants that would also need initial_refresh_token and initial_access_token encrypted, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request.
Projects
None yet
Development

No branches or pull requests

2 participants