From a4553bf05bf6d7ad398c28b5a7a23111a15f2e65 Mon Sep 17 00:00:00 2001 From: Mahesh Makani Date: Fri, 20 Oct 2023 09:02:39 +0100 Subject: [PATCH] refactor(okta): switch from `sid` cookie to `idx` cookie in tests and documentation --- .../integration/ete-okta/jobs_terms.4.cy.ts | 2 ++ .../ete-okta/reauthenticate.4.cy.ts | 20 ++++++++-------- .../ete-okta/registration_2.6.cy.ts | 4 ++-- cypress/integration/ete-okta/sign_in.1.cy.ts | 23 +++++++++++------- cypress/integration/ete-okta/sign_out.5.cy.ts | 1 + .../integration/mocked/okta_register.3.cy.ts | 24 +++++++++---------- .../integration/mocked/okta_sign_in.6.cy.ts | 4 ++-- docs/okta/sessions.md | 2 +- docs/okta/signin.md | 10 ++++---- docs/okta/signout.md | 14 +++++------ docs/okta/tokens.md | 2 +- docs/okta/web-apps-integration-guide.md | 22 ++++++++--------- 12 files changed, 69 insertions(+), 59 deletions(-) diff --git a/cypress/integration/ete-okta/jobs_terms.4.cy.ts b/cypress/integration/ete-okta/jobs_terms.4.cy.ts index 64aeb3291..05ea83122 100644 --- a/cypress/integration/ete-okta/jobs_terms.4.cy.ts +++ b/cypress/integration/ete-okta/jobs_terms.4.cy.ts @@ -30,6 +30,7 @@ describe('Jobs terms and conditions flow in Okta', () => { 'BASE_URI', )}/agree/GRS?returnUrl=https://profile.thegulocal.com/signin?returnUrl=https%3A%2F%2Fm.code.dev-theguardian.com%2F`; cy.setCookie('sid', 'invalid-cookie'); + cy.setCookie('idx', 'invalid-cookie'); cy.visit(termsAcceptPageUrl); cy.url().should( 'include', @@ -175,6 +176,7 @@ describe('Jobs terms and conditions flow in Okta', () => { cy.url().should('include', `/agree/GRS`); // check session cookie is set cy.getCookie('sid').should('exist'); + cy.getCookie('idx').should('exist'); // check idapi cookies are set cy.getCookie('SC_GU_U').should('exist'); cy.getCookie('SC_GU_LA').should('exist'); diff --git a/cypress/integration/ete-okta/reauthenticate.4.cy.ts b/cypress/integration/ete-okta/reauthenticate.4.cy.ts index 195d13cba..7671c2c9e 100644 --- a/cypress/integration/ete-okta/reauthenticate.4.cy.ts +++ b/cypress/integration/ete-okta/reauthenticate.4.cy.ts @@ -35,11 +35,11 @@ describe('Reauthenticate flow, Okta enabled', () => { cy.url().should('include', '/consents/data'); // Get the current session data - cy.getCookie('sid').then((sidCookie) => { - const sid = sidCookie?.value; - expect(sid).to.exist; - if (sid) { - cy.getCurrentOktaSession({ sid }).then((session) => { + cy.getCookie('idx').then((idxCookie) => { + const idx = idxCookie?.value; + expect(idx).to.exist; + if (idx) { + cy.getCurrentOktaSession({ idx }).then((session) => { expect(session.login).to.equal(emailAddress); }); } @@ -83,11 +83,11 @@ describe('Reauthenticate flow, Okta enabled', () => { cy.url().should('include', '/consents/data'); // Get the current session data - cy.getCookie('sid').then((sidCookie) => { - const sid = sidCookie?.value; - expect(sid).to.exist; - if (sid) { - cy.getCurrentOktaSession({ sid }).then((session) => { + cy.getCookie('idx').then((idxCookie) => { + const idx = idxCookie?.value; + expect(idx).to.exist; + if (idx) { + cy.getCurrentOktaSession({ idx }).then((session) => { expect(session.login).to.equal(emailAddressB); }); } diff --git a/cypress/integration/ete-okta/registration_2.6.cy.ts b/cypress/integration/ete-okta/registration_2.6.cy.ts index 62b43aa33..c3dfd6e9f 100644 --- a/cypress/integration/ete-okta/registration_2.6.cy.ts +++ b/cypress/integration/ete-okta/registration_2.6.cy.ts @@ -617,8 +617,8 @@ describe('Registration flow - Split 2/2', () => { cy.url().should('include', '/consents'); // Get the current session data - cy.getCookie('sid').then((originalSidCookie) => { - expect(originalSidCookie).to.exist; + cy.getCookie('idx').then((originalIdxCookie) => { + expect(originalIdxCookie).to.exist; // Visit register again cy.visit( diff --git a/cypress/integration/ete-okta/sign_in.1.cy.ts b/cypress/integration/ete-okta/sign_in.1.cy.ts index 861afc776..085007b16 100644 --- a/cypress/integration/ete-okta/sign_in.1.cy.ts +++ b/cypress/integration/ete-okta/sign_in.1.cy.ts @@ -132,7 +132,7 @@ describe('Sign in flow, Okta enabled', () => { // Get the refreshed session data cy.getCookie('idx').then((newIdxCookie) => { expect(newIdxCookie).to.exist; - // `idx` cookie doesn't have same value as original when refreshed, which is different to the Okta Classic `sid` cookie + // `idx` cookie doesn't have same value as original when refreshed, which is different to the Okta Classic `idx` cookie expect(newIdxCookie?.value).to.not.equal(orignalIdxCookie?.value); // we want to check the cookie is being set as a persistent cookie and not a session cookie, hence the expiry check expect(newIdxCookie?.expiry).to.exist; @@ -162,10 +162,10 @@ describe('Sign in flow, Okta enabled', () => { cy.url().should('include', '/consents'); // Get the current session data - cy.getCookie('sid').then((sidCookie) => { + cy.getCookie('idx').then((idxCookie) => { // Close the user's current session in Okta cy.closeCurrentOktaSession({ - sid: sidCookie?.value, + idx: idxCookie?.value, }).then(() => { // Refresh our user session cy.visit( @@ -194,7 +194,7 @@ describe('Sign in flow, Okta enabled', () => { cy.get('[data-cy="main-form-submit-button"]').click(); cy.url().should('include', '/consents'); - // Delete the Okta sid cookie + // Delete the Okta sid/idx cookie // strange behaviour form Cypress 12 // where we need to delete cookie from both domains // to get the test to pass @@ -206,6 +206,12 @@ describe('Sign in flow, Okta enabled', () => { cy.clearCookie('sid', { domain: `.${Cypress.env('BASE_URI')}`, }); + cy.clearCookie('idx', { + domain: Cypress.env('BASE_URI'), + }); + cy.clearCookie('idx', { + domain: `.${Cypress.env('BASE_URI')}`, + }); // Visit the refresh endpoint cy.visit( @@ -216,6 +222,7 @@ describe('Sign in flow, Okta enabled', () => { cy.url().should('include', '/reset-password'); cy.getCookie('sid').should('not.exist'); + cy.getCookie('idx').should('not.exist'); }, ); }); @@ -334,8 +341,8 @@ describe('Sign in flow, Okta enabled', () => { cy.contains('Change email address'); // Ensure the user's authentication cookies are not set - cy.getCookie('sid').then((sidCookie) => { - expect(sidCookie).to.not.exist; + cy.getCookie('idx').then((idxCookie) => { + expect(idxCookie).to.not.exist; cy.checkForEmailAndGetDetails( emailAddress, @@ -387,8 +394,8 @@ describe('Sign in flow, Okta enabled', () => { cy.url().should('include', '/consents'); // Get the current session data - cy.getCookie('sid').then((originalSidCookie) => { - expect(originalSidCookie).to.exist; + cy.getCookie('idx').then((originalIdxCookie) => { + expect(originalIdxCookie).to.exist; // Visit sign in again cy.visit( diff --git a/cypress/integration/ete-okta/sign_out.5.cy.ts b/cypress/integration/ete-okta/sign_out.5.cy.ts index 601884ec9..0c73c1e31 100644 --- a/cypress/integration/ete-okta/sign_out.5.cy.ts +++ b/cypress/integration/ete-okta/sign_out.5.cy.ts @@ -29,6 +29,7 @@ describe('Sign out flow', () => { cy.url().should('include', `/consents/data`); // check session cookie is set cy.getCookie('sid').should('exist'); + cy.getCookie('idx').should('exist'); // check idapi cookies are set cy.getCookie('SC_GU_U').should('exist'); cy.getCookie('SC_GU_LA').should('exist'); diff --git a/cypress/integration/mocked/okta_register.3.cy.ts b/cypress/integration/mocked/okta_register.3.cy.ts index 23aba9e75..d409900c6 100644 --- a/cypress/integration/mocked/okta_register.3.cy.ts +++ b/cypress/integration/mocked/okta_register.3.cy.ts @@ -1,6 +1,6 @@ describe('Okta Register flow', () => { - const setSidCookie = () => { - cy.setCookie('sid', `the_sid_cookie`, { + const setIdxCookie = () => { + cy.setCookie('idx', `the_idx_cookie`, { domain: Cypress.env('BASE_URI'), }); }; @@ -14,7 +14,7 @@ describe('Okta Register flow', () => { cy.disableCMP(); }); - it('should redirect to homepage if the sid Okta session cookie is valid', () => { + it('should redirect to homepage if the idx Okta session cookie is valid', () => { cy.mockPattern( 200, { @@ -40,7 +40,7 @@ describe('Okta Register flow', () => { cy.visit('/register'); - setSidCookie(); + setIdxCookie(); cy.get('input[name="email"]').type('example@example.com'); cy.mockNext(200, { @@ -71,12 +71,12 @@ describe('Okta Register flow', () => { cy.contains('Sign in with a different email'); }); - it('should redirect to /reauthenticate if the sid Okta session cookie is set, but invalid', () => { + it('should redirect to /reauthenticate if the idx Okta session cookie is set, but invalid', () => { cy.mockPattern(404, '/api/v1/sessions/me'); cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - setSidCookie(); + setIdxCookie(); // visit healthcheck to set the cookie cy.visit('/healthcheck'); @@ -85,7 +85,7 @@ describe('Okta Register flow', () => { cy.location('pathname').should('eq', '/reauthenticate'); - cy.getCookie('sid').should('not.exist'); + cy.getCookie('idx').should('not.exist'); }); }); @@ -93,7 +93,7 @@ describe('Okta Register flow', () => { beforeEach(() => { cy.mockPurge(); }); - it('should redirect to homepage if the sid Okta session cookie is valid', () => { + it('should redirect to homepage if the idx Okta session cookie is valid', () => { cy.mockPattern( 200, { @@ -115,7 +115,7 @@ describe('Okta Register flow', () => { cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - setSidCookie(); + setIdxCookie(); // disable the cmp on the redirect cy.disableCMP(); @@ -138,12 +138,12 @@ describe('Okta Register flow', () => { cy.contains('Sign in with a different email'); }); - it('should redirect to /reauthenticate if the sid Okta session cookie is set but invalid', () => { + it('should redirect to /reauthenticate if the idx Okta session cookie is set but invalid', () => { cy.mockPattern(404, '/api/v1/sessions/me'); cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - setSidCookie(); + setIdxCookie(); // visit healthcheck to set the cookie cy.visit('/healthcheck'); @@ -152,7 +152,7 @@ describe('Okta Register flow', () => { cy.location('pathname').should('eq', '/reauthenticate'); - cy.getCookie('sid').should('not.exist'); + cy.getCookie('idx').should('not.exist'); }); }); }); diff --git a/cypress/integration/mocked/okta_sign_in.6.cy.ts b/cypress/integration/mocked/okta_sign_in.6.cy.ts index feb210c0e..67e13198f 100644 --- a/cypress/integration/mocked/okta_sign_in.6.cy.ts +++ b/cypress/integration/mocked/okta_sign_in.6.cy.ts @@ -26,7 +26,7 @@ describe('Sign in flow', () => { cy.mockPattern(204, {}, '/api/v1/users/userId/sessions'); - cy.setCookie('sid', `the_sid_cookie`); + cy.setCookie('idx', `the_idx_cookie`); // disable the cmp on the redirect cy.disableCMP(); @@ -85,7 +85,7 @@ describe('Sign in flow', () => { '/api/v1/apps/123', ); - cy.setCookie('sid', `the_sid_cookie`); + cy.setCookie('idx', `the_idx_cookie`); // disable the cmp on the redirect cy.disableCMP(); diff --git a/docs/okta/sessions.md b/docs/okta/sessions.md index d11ff74a5..3a0a93aea 100644 --- a/docs/okta/sessions.md +++ b/docs/okta/sessions.md @@ -17,7 +17,7 @@ There are two layers of session in Okta in the following hierarchy. We generally call this the Global Session or Okta session internally, but is known in the Okta documentation as the IdP Session. -The IdP session is the session that is created when a user logs in to or authenticates with Okta with their credentials and any various MFA options. This session is created by Okta and is identified by a cookie named `sid` in the browser. +The IdP session is the session that is created when a user logs in to or authenticates with Okta with their credentials and any various MFA options. This session is created by Okta and is identified by a cookie named `idx` in the browser, and `sid` cookie may also be set, but this is a legacy Okta identification cookie. This cookie is only valid on the Okta/login domain (at the Guardian this is the `profile` subdomain, e.g. https://profile.theguardian.com), and the value is the session id. This same session id which can be used by the [Okta Sessions API](https://developer.okta.com/docs/reference/api/sessions/). This is an opaque value, and only authenticates the user, it does not contain any information about the user, and should not be used to authenticate the user on any API. diff --git a/docs/okta/signin.md b/docs/okta/signin.md index 37fb78c2a..7e7a97356 100644 --- a/docs/okta/signin.md +++ b/docs/okta/signin.md @@ -19,7 +19,7 @@ Throughout the implementation of the sign in code, there are many in line commen In general the steps of sign in with email and password are summarised as follows, assuming okta is enabled, this does not detail all technical requirements, just the main high level: - User navigates to `/signin` - - Check `sid` cookie for existing Okta session, if this is present it checks if the session is still valid. + - Check `idx` cookie for existing Okta session, if this is present it checks if the session is still valid. - If the current session does not exist, or is invalid, sign in page shown - user enters email and password, makes request to gateway `POST /signin` - Use okta authenticate endpoint with the email and password `/api/v1/authn` @@ -41,7 +41,7 @@ sequenceDiagram participant Okta participant Identity API Browser->>Gateway: Request gateway /signin - note over Gateway: Sign in session is checked by inspecting the `sid` cookie + note over Gateway: Sign in session is checked by inspecting the `idx` cookie Gateway->>Okta: GET /api/v1/sessions/:sessionId Okta->>Gateway: return invalid session response Gateway->>Browser: render sign in page @@ -74,7 +74,7 @@ sequenceDiagram participant Okta participant Identity API Browser->>Gateway: Request gateway /signin - note over Gateway: Sign in session is checked by inspecting the `sid` cookie and validating the session against the Okta session API + note over Gateway: Sign in session is checked by inspecting the `idx` cookie and validating the session against the Okta session API note over Okta: check for existing session
in this case a session exists Gateway->>Okta: GET /api/v1/sessions/:sessionId Okta->>Gateway: return valid session response @@ -103,7 +103,7 @@ participant Social participant Identity API Browser ->> Gateway: Request /signin (or /register),
will just be referred to as /signin or sign in for
simplicity, as the same thing happens
in either case -note over Gateway: Sign in session is checked by inspecting the `sid` cookie +note over Gateway: Sign in session is checked by inspecting the `idx` cookie Gateway ->> Okta: GET /api/v1/sessions/:sessionId Okta ->> Gateway: Return invalid session response Gateway ->> Browser: Render sign in page @@ -156,7 +156,7 @@ participant Social participant Identity API Browser ->> Gateway: Request /signin (or /register),
will just be referred to as /signin or sign in for
simplicity, as the same thing happens
in either case -note over Gateway: Sign in session is checked by inspecting the `sid` cookie +note over Gateway: Sign in session is checked by inspecting the `idx` cookie Gateway ->> Okta: GET /api/v1/sessions/:sessionId Okta ->> Gateway: Return invalid session response Gateway ->> Browser: Render sign in page diff --git a/docs/okta/signout.md b/docs/okta/signout.md index d806ceba8..5dff0ad0d 100644 --- a/docs/okta/signout.md +++ b/docs/okta/signout.md @@ -1,7 +1,5 @@ # Sign Out with Okta -Status: RFC - This document describes how we've implemented the sign out flow with Okta in Gateway. In old (current) Identity land, when a user clicks the "sign out" button on the website, or visit the sign out link directly, if they have a valid session, determined using the `SC_GU_U` cookie, Identity API will invalidate all existing user sessions on all devices and browsers, and clear all Identity and related dotcom cookies on the current browser. @@ -12,15 +10,17 @@ Okta provides an administrative API endpoint within the Users API to clear all u https://developer.okta.com/docs/reference/api/users/#clear-user-sessions -This endpoint requires the okta `userId`, which we get from the okta sessions api using the current okta session id cookie `sid` +This endpoint requires the okta `userId`, which we get from the okta sessions api using the current okta session id cookie `idx` We then use this user id to clear all Okta sessions for that user. -In the sceario where the okta call to remove the fails (and we have a orphaned session), we delete the sid cookie anyway and we plan to allow the users to see their active sessions in manage my account section of the site where they can remove them manually. +In the scenario where the okta call to remove the session fails (and we have a orphaned session), we delete the `idx` cookie anyway, and we plan in the future to allow the users to see their active sessions in manage my account section of the site where they can remove them manually. If there is no existing session then we'll render the sign in page. -During the dual running of the system we also need to clear the Identity session too. We can use this access token or the SC_GU_U cookie to call Identity API (TBD which option) to do this. +During the dual running of the system we also need to clear the Identity session too, we do this by calling the `/unauth` endpoint on the Identity API, which will clear the IDAPI session using the`SC_GU_U` cookie. + +We then set the `GU_SO` which identifies that the user has signed out recently. ### User has existing session @@ -36,7 +36,7 @@ sequenceDiagram Gateway->>Okta: Clear user sessions
using userId
DELETE /api/v1/users/${userId}/sessions?oauthTokens=true Okta->>Gateway: Return 204 No Content opt Clear Identity Session (dual running) - Gateway->>Identity API: POST /unauth with SC_GU_U cookie + Gateway->>Identity API: POST /unauth with `SC_GU_U` cookie Identity API->>Gateway: Return GU_SO cookie end Gateway->>Browser: Redirect to returnUrl
clear identity cookies @@ -50,7 +50,7 @@ sequenceDiagram participant Gateway participant Okta Browser->>Gateway: GET /signout?returnUrl=... - note over Gateway: Check the presence of the SC_GU_U cookie and sid cookie,
if neither are present they are not logged in + note over Gateway: Check the presence of the `SC_GU_U` cookie and `idx` cookie,
if neither are present they are not logged in Gateway->>Browser: Redirect request to gateway `/signin` Browser->>Gateway: Request gateway /signin note over Gateway: Sign in session is checked
(encryptedState.signInRedirect == true)
remove/set to false from the encryptedState diff --git a/docs/okta/tokens.md b/docs/okta/tokens.md index 6f22f2cee..115a95943 100644 --- a/docs/okta/tokens.md +++ b/docs/okta/tokens.md @@ -138,7 +138,7 @@ However, public clients such as browser-based applications have a much higher ri Refresh tokens are valid for 90 days by default, but can be configured to be longer or shorter, from 5 mins to unlimited, we also recommend that you rotate the refresh token after each use. -Refresh tokens provide an additional "session" layer on top of the in browser `sid` cookie (which is set after login in browser). Meaning these two sessions are independent of each other, and can be used to manage the user's session in different ways. You can see the [Sessions](sessions.md) documentation for more information on how we use these sessions. +Refresh tokens provide an additional "session" layer on top of the in browser `idx` cookie (which is set after login in browser). Meaning these two sessions are independent of each other, and can be used to manage the user's session in different ways. You can see the [Sessions](sessions.md) documentation for more information on how we use these sessions. ### Token management diff --git a/docs/okta/web-apps-integration-guide.md b/docs/okta/web-apps-integration-guide.md index ef79b3508..95af35671 100644 --- a/docs/okta/web-apps-integration-guide.md +++ b/docs/okta/web-apps-integration-guide.md @@ -171,7 +171,7 @@ In general the following rules apply: 2. If the access token and id token are expired, but the reader has a valid `GU_U` cookie, they might be signed in (`maybeLoggedIn`). - We can't be definitely sure if they are signed in until they attempt to go through the Authorization Code flow to attempt to get a new access token and id token. - - This is where the Global session cookie will be checked as part of the Authorization Code flow, which is the `sid` cookie which is only valid on `profile` subdomain and managed by Okta. + - This is where the Global session cookie will be checked as part of the Authorization Code flow, which is the `idx` cookie which is only valid on `profile` subdomain and managed by Okta. 3. If the reader has neither a valid access token and id token, nor a valid `GU_U` cookie, they are signed out. 4. If the reader has a valid GU_SO cookie, they have recently signed out. Any existing access token and id token should be cleared when the page loads. @@ -180,21 +180,21 @@ By using this `isLoggedIn`/`maybeLoggedIn` and `isSignedOut` states, we can avoi We already set a `GU_U` cookie which is valid across all Guardian domains, but is not used to take any actions on behalf of the reader (this is performed by the secure, httpOnly, `SC_GU_U` cookie). We can keep using this cookie to determine if the reader is `maybeLoggedIn`. -The `GU_U` cookie and the `sid` cookie will lead to the following scenarios: +The `GU_U` cookie and the `idx` cookie will lead to the following scenarios: -- `GU_U` && `sid` +- `GU_U` && `idx` - Signed in fully state, can complete authorization flow and get tokens -- `GU_U` && !`sid` +- `GU_U` && !`idx` - - `sid` cookie expired or deleted + - `idx` cookie expired or deleted - Error when attempting to get new tokens, can delete `GU_U` cookie to get to signed out state -- !`GU_U` && `sid` +- !`GU_U` && `idx` - `GU_U` cookie expired or deleted, similar to signed out state - When a user goes to log in they will see “signed in as” screen, or be silently logged in if not using “prompt=login” parameter -- !`GU_U` && !`sid` +- !`GU_U` && !`idx` - Signed out state @@ -212,7 +212,7 @@ We can also refresh tokens anytime when the reader has valid access token and id In Okta land "Sign In" can mean two different things. More information about this can be seen in the [Sessions](sessions.md) document. But in general there are two sign in states: -1. The global Okta session (where the `sid` cookie is set) +1. The global Okta session (where the `idx` cookie is set) 2. The application session (where OAuth tokens come into play) It's possible to sign into each one individually, but in most cases we want to sign into the application session only when the global Okta session is valid. @@ -225,7 +225,7 @@ This gives us two options on how to do "sign in": 1. Sign the user into the global session first, and then the application. - Sign in links use `profile.theguardian.com/signin?returnUrl=` to sign the user in. - - Once the user authenticates and the global session is set (namely the `sid` cookie, and the `GU_U` cookie), the user is redirected back to the application, and the application can then go through the Authorization Code flow to get the tokens, whether through redirect method or the iframe method. + - Once the user authenticates and the global session is set (namely the `idx` cookie, and the `GU_U` cookie), the user is redirected back to the application, and the application can then go through the Authorization Code flow to get the tokens, whether through redirect method or the iframe method. - This provides a separation of concerns between the global session and the application session. - The same logic from the 2nd bullet point can be used to get and refresh access/id tokens. - Two step process may cause complications if this isn't set up correctly, e.g. lots of redirects, or the user is signed in to the global session but not the application session, etc. @@ -251,7 +251,7 @@ participant Okta note over Browser: A user clicks "sign in" Browser->>Gateway: GET profile.theguardian.com/signin?returnUrl= -note over Gateway: global okta
session check
"sid" cookie +note over Gateway: global okta
session check
"idx" cookie alt no existing session Gateway->>Browser: Load sign in/register page note over Browser: User sign in with
email+password/social/set password
session set in browser
redirect to returnUrl @@ -284,7 +284,7 @@ participant Okta Browser->>SDK: A user clicks "sign in" SDK->>Browser: Setup /authorize url with required
parameters to start the
Authorization Code flow (with PKCE) Browser->>Okta: Request OAuth /authorize -note over Okta: global okta
session check
"sid" cookie +note over Okta: global okta
session check
"idx" cookie opt no existing session Okta->>Browser: Return /login/login.html note over Browser: Load html, run JS redirect
to /signin with fromURI and clientId params