Skip to content

Commit

Permalink
Update End-to-End Testing -> Auth0 Authentication docs for v12 (#4895)
Browse files Browse the repository at this point in the history
Co-authored-by: Bill Glesias <bglesias@gmail.com>
  • Loading branch information
debrisapron and AtofStryker committed Dec 5, 2022
1 parent 432d0dd commit 9eacc8e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 29 deletions.
Binary file added assets/img/examples/auth0-origin.mp4
Binary file not shown.
Binary file added assets/img/examples/auth0-session-restore.mp4
Binary file not shown.
185 changes: 156 additions & 29 deletions content/guides/end-to-end-testing/auth0-authentication.md
Expand Up @@ -7,25 +7,15 @@ e2eSpecific: true

## <Icon name="graduation-cap"></Icon> What you'll learn

- Log in to [Auth0](https://auth0.com) through the UI with
[`cy.origin()`](/api/commands/origin)
- Programmatically authenticate with [Auth0](https://auth0.com) via a custom
Cypress command
- Adapting your [Auth0](https://auth0.com) application for programmatic
- Adapt your [Auth0](https://auth0.com) application for programmatic
authentication during testing

</Alert>

<Alert type="success">

<strong class="alert-header">Why authenticate programmatically?</strong>

Typically, logging in a user within your app by authenticating via a third-party
provider requires visiting login pages hosted on a different domain. Since each
Cypress test is limited to visiting domains of the same origin, we can subvert
visiting and testing third-party login pages by programmatically interacting
with the third-party authentication API to login a user.

</Alert>

<Alert type="warning">

This guide is setup for testing against an [Auth0](https://auth0.com) Single
Expand All @@ -36,6 +26,22 @@ for automated end-to-end testing.

</Alert>

<Alert type="success">

<strong class="alert-header">Authenticate by visiting a different domain with
[`cy.origin()`](/api/commands/origin)</strong>

Typically, logging in a user within your app by authenticating via a third-party
provider requires visiting a login page hosted on a different domain. Before
Cypress [v12.0.0](https://on.cypress.io/changelog#12-0-0), Cypress tests were
limited to visiting domains of the same origin, making programmatic login the
only option for authenticating users with a third-party API. As of Cypress
[v12.0.0](https://on.cypress.io/changelog#12-0-0), Cypress tests are no longer
limited to visiting domains of a single origin, meaning you can easily
authenticate with [Auth0](https://auth0.com) via the UI!

</Alert>

## Auth0 Application Setup

To get started with Auth0, an application needs to be setup within the
Expand Down Expand Up @@ -111,23 +117,151 @@ require('dotenv').config()
}
```

Note that `auth0_client_secret` is only needed for
[programmatic login](#Programmatic-Login).

:::

## Custom Command for Auth0 Authentication

There are two ways you can authenticate to Auth0:

- [Login with `cy.origin()`](#Login-with-cy-origin)
- [Programmatic Login](#Programmatic-Login)

### Login with [`cy.origin()`](/api/commands/origin)

Next, we'll write a custom command called `loginToAuth0` to perform a login to
[Auth0](https://auth0.com). This command will use
[`cy.origin()`](/api/commands/origin) to

1. Navigate to the Auth0 login
2. Input user credentials
3. Sign in and redirect back to the
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app)
4. Cache the results with [`cy.session()`](/api/commands/session)

```js
// cypress/support/auth-provider-commands/auth0.ts

function loginViaAuth0Ui(username: string, password: string) {
// App landing page redirects to Auth0.
cy.visit('/')

// Login on Auth0.
cy.origin(
Cypress.env('auth0_domain'),
{ args: { username, password } },
({ username, password }) => {
cy.get('input#username').type(username)
cy.get('input#password').type(password, { log: false })
cy.contains('button[value=default]', 'Continue').click()
}
)

// Ensure Auth0 has redirected us back to the RWA.
cy.url().should('equal', 'http://localhost:3000/')
}

Cypress.Commands.add('loginToAuth0', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'AUTH0 LOGIN',
message: [`🔐 Authenticating | ${username}`],
// @ts-ignore
autoEnd: false,
})
log.snapshot('before')

loginViaAuth0Ui(username, password)

log.snapshot('after')
log.end()
})
```

Now, we can use our `loginToAuth0` command in the test. Below is our test to
login as a user via Auth0 and run a basic sanity check.

<Alert type="success">

The
[runnable version of this test](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui-auth-providers/auth0.spec.ts)
is in the
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app).

</Alert>

```js
describe('Auth0', function () {
beforeEach(function () {
cy.task('db:seed')
cy.intercept('POST', '/graphql').as('createBankAccount')
cy.loginToAuth0(
Cypress.env('auth0_username'),
Cypress.env('auth0_password')
)
cy.visit('/')
})

it('shows onboarding', function () {
cy.contains('Get Started').should('be.visible')
})
})
```

<DocsVideo src="/img/examples/auth0-origin.mp4"></DocsVideo>

Lastly, we can refactor our login command to take advantage of
[`cy.session()`](/api/commands/session) to store our logged in user so we don't
have to reauthenticate before every test.

```js
Cypress.Commands.add('loginToAuth0', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'AUTH0 LOGIN',
message: [`🔐 Authenticating | ${username}`],
// @ts-ignore
autoEnd: false,
})
log.snapshot('before')

cy.session(
`auth0-${username}`,
() => {
loginViaAuth0Ui(username, password)
},
{
validate: () => {
// Validate presence of access token in localStorage.
cy.wrap(localStorage)
.invoke('getItem', 'authAccessToken')
.should('exist')
},
}
)

log.snapshot('after')
log.end()
})
```

<DocsVideo src="/img/examples/auth0-session-restore.mp4"></DocsVideo>

### Programmatic Login

Below is a command to programmatically login into [Auth0](https://auth0.com),
using the
[/oauth/token endpoint](https://auth0.com/docs/protocols/protocol-oauth2#token-endpoint)
and set an item in localStorage with the authenticated users details, which we
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 `loginByAuth0Api` command will execute the following steps:

1. Use the
[/oauth/token endpoint](https://auth0.com/docs/protocols/protocol-oauth2#token-endpoint)
to perform the programmatic login.
2. Finally the `auth0Cypress` localStorage item is set with the `access token`,
`id_token` and user profile.
2. Finally the `auth0Cypress` `localStorage` item is set with the
`access token`, `id_token` and user profile.

```jsx
// cypress/support/commands.js
Expand Down Expand Up @@ -216,17 +350,6 @@ describe('Auth0', function () {
})
```

<Alert type="success">

<strong class="alert-header">Try it out</strong>

The
[runnable version of this test](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui-auth-providers/auth0.spec.ts)
is in the
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app).

</Alert>

## Adapting an Auth0 App for Testing

<Alert type="info">
Expand All @@ -238,7 +361,11 @@ within Cypress tests. To use this practice it is assumed you are testing an app
appropriately built or adapted to use Auth0.

The following sections provides guidance on building or adapting an app to use
Auth0 authentication.
Auth0 authentication. Please note that if you are
[logging in with `cy.origin()`](#Login-with-cy-origin) and your app is already
successfully integrated with Auth0, you do not need to make any further changes
to your app and the remainder of this guide should be regarded as purely
informational.

</Alert>

Expand Down Expand Up @@ -420,7 +547,7 @@ if (process.env.REACT_APP_AUTH0) {

An update to our
[AppAuth0.tsx component](https://github.com/cypress-io/cypress-realworld-app/blob/develop/src/containers/AppAuth0.tsx)
is needed to conditionally use the `auth0Cypress` localStorage item.
is needed to conditionally use the `auth0Cypress` `localStorage` item.

In the code below, we conditionally apply a `useEffect` block based on being
under test with Cypress (using `window.Cypress`).
Expand Down

0 comments on commit 9eacc8e

Please sign in to comment.