diff --git a/docs/app/guides/authentication-testing/descope-authentication.mdx b/docs/app/guides/authentication-testing/descope-authentication.mdx new file mode 100644 index 0000000000..e7c2b47dc1 --- /dev/null +++ b/docs/app/guides/authentication-testing/descope-authentication.mdx @@ -0,0 +1,262 @@ +--- +title: Descope Authentication +slug: /guides/end-to-end-testing/descope-authentication +--- + +:::info + +## What you'll learn + +- Log in to [Descope](https://descope.com) via the UI +- Programmatically authenticate with [Descope](https://descope.com) via a custom + Cypress command + +::: + +## Descope Application Setup + +To get started with Descope, an application needs to be setup within the +[Descope Dashboard](https://app.descope.com/home) via the following steps: + +1. Visit the [Descope Dashboard](https://app.descope.com/home) and click the + "+ Project" button under the project dropdown. +2. Enter the desired name for your application. +3. Hit the "Create" button + +## Setting Descope app credentials in Cypress + +To have access to test user credentials within our tests we need to configure +Cypress to use the [Descope](https://descope.com) environment variables set in the +`.env` file. + +:::cypress-config-example + +```js +// Populate process.env with values from .env file +require('dotenv').config() +``` + +```js +{ + e2e: { + includeShadowDom: true, // For interacting with Descope components + }, + env: { + descope_project_id: process.env.REACT_APP_DESCOPE_PROJECT_ID, + descope_management_key: process.env.REACT_APP_DESCOPE_MANAGEMENT_KEY + }, +} +``` + +::: + +## Custom Command for Descope Authentication + +There are two ways you can authenticate to Descope: + +- [Login with UI](#Login-with-UI) +- [Programmatic Login](#Programmatic-Login) + +### Initialize test user & variables + +For both UI and programatic login, you'll need to initialize a test user, get your env variables, and create a delete users command. +We'll do this in the `cypress/support/commands.js` file. + +```js +// cypress/support/commands.js + +const projectId = Cypress.env('descope_project_id') +const managementKey = Cypress.env('descope_management_key') +const descopeAPIDomain = 'api.descope.com' + +// Define the authorization header +const authHeader = { + Authorization: `Bearer ${projectId}:${managementKey}`, +} + +// Define the base URL for Descope API +const descopeApiBaseURL = `https://${descopeAPIDomain}/v1` + +const testUserLoginId = + 'testUser' + Math.floor(1000 + Math.random() * 9000) + '@gmail.com' // Must match email to pass validation + +// Define the test user details +const testUser = { + loginId: testUserLoginId, + email: testUserLoginId, + phone: '+11231231234', + verifiedEmail: true, + verifiedPhone: true, + displayName: 'Test User', + test: true, +} +``` + +To clean up the created test users so we don’t go over the test user limit, +we'll have a `deleteAllTestUsers` function. + +```js +// cypress/support/commands.js + +Cypress.Commands.add('deleteAllTestUsers', () => { + cy.request({ + method: 'DELETE', + url: `${descopeApiBaseURL}/mgmt/user/test/delete/all`, + headers: authHeader, + }) +}) +``` + +### Login with UI + +Next, we'll write a custom command called `loginViaDescopeUI` to perform a login to +[Descope](https://descope.com) using the [Test User Management API](https://docs.descope.com/api/testusermanagement/) +and navigating via the user interface. This command will + +1. Navigate to the Descope login +2. Use the [Test User Management API](https://docs.descope.com/api/testusermanagement/) to perform the login (create user and generate OTP code). +3. Enter the user login ID and code that we just generated to log in via the user interface. + +```js +// cypress/support/commands.js + +Cypress.Commands.add('loginViaDescopeUI', () => { + cy.request({ + method: 'POST', + url: `${descopeApiBaseURL}/mgmt/user/create`, + headers: authHeader, + body: testUser, + }).then(({ body }) => { + const loginId = body['user']['loginIds'][0] + cy.request({ + method: 'POST', + url: `${descopeApiBaseURL}/mgmt/tests/generate/otp`, + headers: authHeader, + body: { + loginId: loginId, + deliveryMethod: 'email', + }, + }).then(({ body }) => { + const otpCode = body['code'] + const loginID = body['loginId'] + cy.visit('/login') + cy.get('descope-wc').find('input').type(loginID) + cy.get('descope-wc').find('button').contains('Continue').click() + cy.get('descope-wc') + .find('.descope-input-wrapper') + .find('input') + .should('exist') // Assertion added to wait for the OTP code input to appear + let otpCodeArray = Array.from(otpCode) // Convert the OTP code string to an array + for (var i = 0; i < otpCodeArray.length; i++) { + cy.get('descope-wc') + .find('.descope-input-wrapper') + .find('input') + .eq(i + 1) + .type(otpCodeArray[i], { force: true }) + } + cy.get('descope-wc').find('button').contains('Submit').click() + + // Customize these steps based on your authentication flow + }) + }) +}) +``` + +Now, we can use our `loginViaDescopeUI` command in the test. Below is our test to +login as a user via Descope and run a basic sanity check. + +```js +describe('Descope', function () { + beforeEach(function () { + cy.deleteAllTestUsers() + cy.loginViaDescopeUI() + cy.visit('/') + }) + + it('shows welcome page', function () { + cy.contains('Welcome').should('be.visible') + }) +}) +``` + +### Programmatic Login + +We'll now write a command to programmatically login into +[Descope](https://descope.com) using the [Test User Management API](https://docs.descope.com/api/testusermanagement/) +and set an item in `localStorage` with the authenticated users details, which we +will use in our application code to verify we are authenticated under test. + +The `loginViaDescopeAPI` command will execute the following steps: + +1. Use the [Test User Management API](https://docs.descope.com/api/testusermanagement/) to perform the programmatic login (create user, generate OTP code, and verify OTP code). +2. Set the `refreshToken` and `sessionToken` items in localStorage. + +```jsx +// cypress/support/commands.js +Cypress.Commands.add('loginViaDescopeAPI', () => { + cy.request({ + method: 'POST', + url: `${descopeApiBaseURL}/mgmt/user/create`, + headers: authHeader, + body: testUser, + }).then(({ body }) => { + const loginId = body['user']['loginIds'][0] + cy.request({ + method: 'POST', + url: `${descopeApiBaseURL}/mgmt/tests/generate/otp`, + headers: authHeader, + body: { + loginId: loginId, + deliveryMethod: 'email', + }, + }).then(({ body }) => { + const otpCode = body['code'] + cy.request({ + method: 'POST', + url: `${descopeApiBaseURL}/auth/otp/verify/email`, + headers: authHeader, + body: { + loginId: loginId, + code: otpCode, + }, + }).then(({ body }) => { + const sessionJwt = body['sessionJwt'] + const refreshJwt = body['refreshJwt'] + + /** Default name for the session cookie name / local storage key */ + const SESSION_TOKEN_KEY = 'DS' + /** Default name for the refresh local storage key */ + const REFRESH_TOKEN_KEY = 'DSR' + + // // Store the JWT in the browser's local storage. + cy.window().then((win) => { + win.localStorage.setItem(SESSION_TOKEN_KEY, sessionJwt) + win.localStorage.setItem(REFRESH_TOKEN_KEY, refreshJwt) + }) + + // // Now navigate to the root URL of your application. + cy.visit('/') + }) + }) + }) +}) +``` + +With our Descope app setup properly in the Descope Developer console, +necessary environment variables in place, and our +`loginViaDescopeAPI` command implemented, we will be able to authenticate +with Descope while our app is under test. Below is +a test to login as a user using our `loginViaDescopeAPI` function and verify the welcome page is showing. + +```jsx +describe('Descope', function () { + beforeEach(function () { + cy.deleteAllTestUsers() + cy.loginViaDescopeAPI() + }) + + it('shows welcome page', function () { + cy.contains('Welcome').should('be.visible') + }) +}) +```