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

eliminate 3rd party cookies in portal authentication #12

Open
mbjones opened this issue Nov 15, 2024 · 1 comment
Open

eliminate 3rd party cookies in portal authentication #12

mbjones opened this issue Nov 15, 2024 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@mbjones
Copy link
Member

mbjones commented Nov 15, 2024

The current d1_portal authentication service creates an OAuth session when people log in, but then returns a 3rd party cookie to the client when the authentication flow is done. We describe how to completely switch to OAuth2 in our OAuth2 Refactor document, but this will require rewriting all of our client tools to send OAuth2 rather than JWT tokens, and refactor all of our services to trust those access_tokens.

An alternative transitional flow would be to support both the OAuth2/OpenID token and the DataONE token. Currently, when a client like MetacatUI authenticates, it 1) redirects the browser to the CN portal to authenticate, which store the successful OAuth login in a session and returns a cookie, and 2) the client uses that cookie to retrieve a DataONE JWT token which can be used against all DataONE-compliant services. To refactor this, we could:

  • Client authenticates against an OAuth2 provider (like Keycloak) that delegates to ORCID (or other OpenID Connect providers) using OAuth2 and receives and OpenID connect access_token and refresh_token
  • Client sends OpenID access_token to DataONE /portal/token to retrieve a DataONE token (rather than a cookie, and this would require a service change for the portal service to trust the OpenID token)
  • Client sends DataONE JWT token to any service that needs it as normal

This eliminates the use of a third party cookie, but still uses a custom DataONE token so that all of the services still work without having to accept OpenID access_tokens. Over time, we could introduce support for OpenID tokens in these services, and then gradually switch from DataONE tokens to OpenID tokens.

Proposed alternate OAuth2 pathway

🚧 Work in Progress 🚧

In particular, the goal is to eliminate the use of JSESSIONID and hazelcast.sessionId cookies in d1_portal, and instead replace those with another mechanism. The proposal is that the initial authentication will be handled by Keycloak OAuth2/OpenIDConnect, which will use PKCE authentication so that the MetacatUI browser single-page application client can securely authenticate against ORCID or other OAuth2 providers. This will return both an access _token and a refresh_token to MetacatUI. d1_portal will be rewritten to trust this access_token, which can be used to call the d1_portal /token endpoint to retrieve a DataONE JWT token. MetacatUI can then use DataONE JWT to interact with all other DataONE services as normal, without them having to be rewritten. The only service that will need to be refactored is d1_portal and MetacatUI.

Compared with the current d1_portal sequence diagram, our new process would look something like this (this seq diagram is not fully correct and needs some more work before it completely captures the process):

sequenceDiagram
    
    participant Browser
    participant ADC
    participant keycloak
    participant d1_portal
    participant d1_account
    participant ORCID
    
    Browser->>+ADC: GET /catalog
    ADC-->>-Browser: MetacatUIApp

Browser->>+keycloak: POST /realms/DataONE/account(scope, PKCE_challenge)
    keycloak->>keycloak: performORCIDAuth()
    keycloak-->>-Browser: 302 Redirect (auth_code, Location)
    
Browser->>+keycloak: POST /realms/DataONE/token(auth_code, PKCE_challenge)
    keycloak->>+ORCID: getAccessToken (code, client_id, client_secret)
    ORCID-->>-keycloak: access_token, refresh_token, expiresIn, scope, orcid, name, {email}
    keycloak-->>-Browser: 302 Redirect (access_token, refresh_token, data_attributes?)

    Browser->>+d1_portal: GET /portal/token(access_token, state=getDataONEToken)
    d1_portal->>d1_portal: validateAccessToken(access_token)
    d1_portal->>d1_portal: parseAccessToken (access_token): data_attributes
    d1_portal->>d1_portal: SessionHelper saveMap(session_id, sessionMap)
    d1_portal->>+d1_account: registerAccount(person)
    d1_account-->>-d1_portal: response
    d1_portal-->>-Browser: 302 Redirect (target)

    Browser->>+ADC: GET /catalog
    ADC-->>-Browser: MetacatUIApp

    Browser->>+d1_portal: GET /token (access_token)
    d1_portal->>d1_portal: validateAccessToken(access_token)
    d1_portal-->>-Browser: 302 Redirect (DataONE_JWT)
Loading

One side benefit of this approach is that MetacatUI can monitor the expiration date of the DataONE_JWT token, and can use the access_token to retrieve a new DataONE JWT to extend the session time, all without another user login. In addition, the access_token itself can expire, in which case MetacatUI can use the refresh_token to retrieve a new access_token. Only when the refresh_token expires or is revoked would the user need to log in again.

@mbjones
Copy link
Member Author

mbjones commented Feb 1, 2025

Initial Keycloak deployment prototyping in DataONEorg/dataone-keycloak#1

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

No branches or pull requests

1 participant