# OAuth 2.0 Sign-In: Browser and Device Code

> Many APIs sit behind an identity provider such as Microsoft Entra ID, Okta, Auth0, Keycloak or Google, and only accept a token issued to a signed-in user. You cannot get that token with a client ID and secret alone.

Source: https://totalshiftleft.ai/help-center/product-documentation/oauth-2-interactive-sign-in

> **Applies to:** All editions · Web app and Desktop app · Access to the project's settings

## Overview

Many APIs sit behind an identity provider such as Microsoft Entra ID, Okta, Auth0, Keycloak or Google, and only accept a token issued to a signed-in **user**. You cannot get that token with a client ID and secret alone. A person has to sign in, often with multi-factor authentication (MFA).

Shift-Left Studio lets you sign in to an OAuth 2.0 authentication profile once, in your own browser or with a short code. After that, Studio keeps the sign-in alive: it refreshes the access token before it expires, so every test run, pack, schedule and CI run gets a valid token without anyone signing in again.

This article covers the two interactive flows, **Authorization Code (sign in with browser)** and **Device Code (sign in with a code)**. For the other OAuth 2.0 flows and every other method, see [Authentication Profiles: Choose and Configure a Method](/help-center/product-documentation/authentication-profiles-and-methods).

## Key concepts

| Term | Meaning |
|---|---|
| Authorization Code with PKCE | The standard way to sign in as a user. Studio opens the provider's sign-in page; the provider sends a one-time code back to Studio, which exchanges it for tokens. PKCE protects that exchange, so a public client needs no client secret. |
| Device Code | You see a short code in Studio and type it at the provider's page on any device. No redirect is involved. |
| Redirect URI | The address the provider sends you back to after sign-in. There is exactly one per installation, and Studio shows it to you. |
| Access token | The short-lived token sent as `Authorization: Bearer …` on each request. |
| Refresh token | A longer-lived token Studio uses to get new access tokens. Requires the `offline_access` scope with most providers. |
| Grant | The result of a sign-in: who signed in, the tokens, and when they expire. |

## Which flow should I use?

| Flow | Use it when | Needs a person? | Renews itself? |
|---|---|---|---|
| **Authorization Code (sign in with browser)** | You test as a user. The standard choice. MFA and passkeys work. | Once | Yes |
| **Device Code (sign in with a code)** | The browser cannot reach Studio's sign-in callback (a proxy, a locked-down desktop, a remote server). | Once | Yes |
| **Client Credentials (app only, no user)** | The API accepts an application identity, with no user. | Never | Yes (fetches a new token) |
| **Password Grant (username + password)** | Legacy first-party APIs only. Microsoft Entra ID refuses it for accounts with MFA or Conditional Access. | Never | Yes (fetches a new token) |
| **Implicit (legacy — paste a token)** | Avoid. It cannot fetch a token; a pasted token expires. | — | No |

## Before you begin

- Register an application with your identity provider and note its **Client ID**. For a browser sign-in, a public client (for example, Entra's "Mobile and desktop applications" platform) needs no client secret.
- Know the scopes the API needs. Include `offline_access` so the provider issues a refresh token.
- Use a dedicated test account. Every run that uses this profile acts as the person who signed in.
- On a server installation, an administrator must have set the server's public address for browser sign-in (see [For administrators](#for-administrators-self-hosted-installations)). Device Code works without it.

## Step 1 — Create the OAuth 2.0 profile

1. Go to **Project Settings** → **Authentication Profiles** and click **New Profile**.
2. Enter a **Profile Name** and click **Next**.
3. Under **Authentication Method**, select **OAuth 2.0**.
4. In **OAuth Flow**, choose **Authorization Code (sign in with browser)** or **Device Code (sign in with a code)**. A short guide under the selector explains the flow you picked.

## Step 2 — Fill in the provider's details

The quickest way is a provider preset:

1. In **Fill in for a provider (optional)**, choose **Microsoft Entra ID (Azure AD)**, **Okta**, **Auth0**, **Keycloak** or **Google**.
2. Fill in the boxes that appear, for example **Tenant (ID or domain)** and **API scope** for Entra, **Okta domain** and **Authorization server** for Okta, **Auth0 domain** and **API audience** for Auth0, or **Keycloak URL** and **Realm** for Keycloak.
3. Click **Fill in**. Studio fills the **Authorization URL**, **Token URL**, **Device Authorization URL**, **Scopes**, and, where the provider has one, the **Token Revocation URL**. Every field stays editable.

Then complete the rest:

4. Enter the **Client ID**.
5. Leave **Client Secret** blank for a public (PKCE) client. Enter it only if your app registration is a confidential client. It is stored encrypted.
6. Check **Scopes**. For a user sign-in they usually look like `openid profile offline_access api://<app-id>/.default`.
7. Set **Client Authentication**. Most providers accept **Send as Basic Auth header**. Microsoft Entra ID expects **Send as request body** (the preset sets this).

| Provider | What the preset adds |
|---|---|
| Microsoft Entra ID | v2.0 endpoints for your tenant, `openid profile offline_access` plus your API scope, request-body client authentication, and `prompt=select_account`. |
| Okta | Endpoints for your authorization server, revocation URL, `openid profile offline_access`. |
| Auth0 | Endpoints, revocation URL, and your API audience on both sign-in and token requests. Without an audience, Auth0 issues a token no API accepts. |
| Keycloak | OpenID Connect endpoints for your realm, revocation URL. |
| Google | Google's endpoints, and `access_type=offline` with `prompt=consent` so a refresh token is returned. |

## Step 3 — Register the redirect URI (browser sign-in only)

In the **Browser sign-in** panel, Studio shows **Register this redirect URI with your provider:** followed by the address. Click **Copy** and add it to your app registration. You do this once per installation.

| Installation | Redirect URI looks like | Register it as (Entra) |
|---|---|---|
| Desktop app | `http://localhost:<port>/api/oauth/callback` | Mobile and desktop applications (any localhost port matches) |
| Cloud or server installation | `https://<your Studio address>/api/oauth/callback` | Web (exact match) |

> **Important:** Use the address Studio shows, not a callback URL from another tool such as Postman. A redirect URI that does not match what is registered is the most common reason a provider rejects the sign-in.

If the panel instead says that browser sign-in needs the server's public address, the server has not been configured for it yet. Use **Device Code** until an administrator sets it.

## Step 4 — Sign in

**Browser sign-in (Authorization Code)**

1. Click **Sign in with browser**. Studio saves the profile first, then opens the provider's sign-in page in your system browser. In the Desktop app this is your default browser, so your existing session, MFA and passkeys work.
2. Sign in at the provider. The panel shows **Waiting for you to finish signing in in your browser…** If the page did not open, click **Open the sign-in page again**.
3. When the browser shows **Signed in**, close it and return to Studio. The panel now reads, for example, **Signed in as jane@contoso.com · access token valid for 59 min · refreshes automatically**.

**Device sign-in (Device Code)**

1. Click **Sign in with a code**.
2. The panel shows **Open the page below on any device and enter this code:**, a code such as `ABCD-EFGH`, and a link.
3. Open the link on any device, type the code, and sign in.
4. The panel updates to **Signed in** on its own.

A sign-in that is not finished within 10 minutes expires. Click **Cancel** to stop waiting, then start again.

5. Click through to **Save Profile**, then **Activate** the profile for its environment if it is not already active.

## What happens on a run

- **The server holds the sign-in.** The refresh token is stored encrypted on the server, and only the server uses it. Providers such as Entra and Okta issue a new refresh token each time one is used (refresh-token rotation); if two parties refreshed the same sign-in, they would invalidate each other.
- **Tokens are cached.** An access token is reused until it is about to expire, so a pack makes one token call, not one per test. When it is close to expiry, the server refreshes it and stores the new refresh token.
- **The local runner (Agent) never gets the refresh token.** It receives a live access token for the run and asks the server for a new one when it runs low.
- **Scheduled runs, packs and CI runs** use the signed-in profile exactly as a manual run does. Nobody needs to be present.
- **Performance runs** read authentication again while they run, so a long soak test keeps a valid token.

## When a refresh fails

| Situation | What Studio does |
|---|---|
| The provider refuses the refresh (password changed, sign-in revoked, refresh token expired) | Marks the profile **Sign-in expired**. Runs fail with "The sign-in for auth profile … has expired or was revoked. Open the profile and sign in again." It never sends a stale token. |
| The provider cannot be reached (network error, provider outage) | Uses the current access token only while it is still valid; otherwise the run fails with a retryable error. The sign-in is not marked expired. |
| The provider issued no refresh token | The sign-in works until the access token expires. The panel says **no refresh token (add offline_access to scopes)**. |

Run analysis reports an expired sign-in as a single cause, **The OAuth sign-in for "<profile name>" has expired**, classed as an environment issue. It never suggests changing your tests, because the tests are not at fault.

## Sign out, or sign in again

- **Sign out** removes the stored tokens. If the profile has a **Token Revocation URL**, Studio also revokes the refresh token at the provider.
- **Sign in again** replaces the current sign-in, for example to switch test accounts.
- On the **Authentication Profiles** list, each interactive profile shows a badge: **Not signed in**, **Signed in as …**, or **Sign-in expired**.

### Editing a signed-in profile

Changing a description or other details keeps the sign-in. Changing anything that decides which token you get (the flow, **Client ID**, **Token URL**, **Authorization URL**, **Device Authorization URL** or **Scopes**) drops the sign-in, and you must sign in again. If you edit the profile while a browser sign-in is in progress, the sign-in is refused with "The profile was edited while you were signing in. Save it, then sign in again."

## Configure it with the Project Assistant

Ask the Project Assistant, for example, "Set up OAuth 2.0 browser sign-in for Entra tenant contoso.onmicrosoft.com with client ID … and scope api://orders/.default, then sign me in." It fills in the flow, URLs, client ID and scopes, reports the redirect URI you must register, and can start the browser or device sign-in. It will not accept a client secret or password in chat, because a secret typed into a conversation stays in the transcript; it opens the **Authentication** screen with the field marked instead. For a public client with no secret, the whole setup and sign-in can be done from the conversation.

## Troubleshooting

| Symptom | Why it happens | What to do |
|---|---|---|
| The provider shows a redirect URI mismatch error | The registered redirect URI differs from the one Studio shows. | Copy the URI from the **Browser sign-in** panel and register it exactly. In Entra, use the platform shown in the table above. |
| **Sign in with browser** is disabled and a message mentions the public address | The server installation has no public address configured. | Ask an administrator to set it, or use **Device Code**. |
| "Interactive sign-in is switched off on this server" | An administrator disabled interactive sign-in. | Paste an access token, or ask the administrator. |
| `invalid_client` during sign-in | Wrong client authentication style, or a confidential client without its secret. | Switch **Client Authentication**, or enter the **Client Secret**. |
| Signed in, but runs fail after about an hour | No refresh token was issued. | Add `offline_access` to **Scopes** (for Google, use the preset) and sign in again. |
| Status shows **Sign-in expired** | The provider revoked or expired the sign-in. | Click **Sign in again**. |
| Runs return 401 even though signed in | The token lacks the scope or audience the API expects. | Check **Scopes**, the Auth0 audience, or **Additional Token Request Body (JSON)**. |
| "This sign-in link was already used" in the browser | The browser tab was reloaded after sign-in finished. | Close it; if the panel is not signed in, start again. |

## Best practices

- Use a dedicated test account, with the same MFA policy as real users, so tests reflect production behavior.
- Always include `offline_access` (or your provider's equivalent) so scheduled runs keep working.
- Set a **Token Revocation URL** so signing out really ends the session at the provider.
- Keep one OAuth profile per environment and identity; do not switch accounts on a shared profile mid-sprint.
- Sign in again well before a planned long run if the provider's refresh tokens have a fixed lifetime.

## FAQ

**Does Studio ever see my password?**
No. With browser and device sign-in, you type your password only on the provider's own page.

**Do I need a client secret?**
Not for a public client using browser or device sign-in. **Client Credentials (app only, no user)** always needs one.

**Who is the signed-in user when a teammate runs a test?**
The person who signed in to the profile. The sign-in belongs to the profile, not to whoever presses Run.

**Does a duplicated profile keep the sign-in?**
No. Sign in to the duplicate separately.

**Can I use this on the Desktop app offline from the provider?**
No. The provider must be reachable to sign in and to refresh tokens.

## Related articles

- [Authentication Profiles: Choose and Configure a Method](/help-center/product-documentation/authentication-profiles-and-methods)
- [Sign In With Steps](/help-center/product-documentation/sign-in-with-steps)
- [Project Settings: Environments and Auth](/help-center/product-documentation/project-settings)
- [Test Run](/help-center/product-documentation/test-run)
- [Project Assistant Overview](/help-center/product-documentation/project-assistant-overview)

## For administrators (self-hosted installations)

| Setting | Default | What switching it does |
|---|---|---|
| `PUBLIC_APP_URL` | not set | The address people use to reach Studio. Required for browser sign-in on a server installation; the redirect URI is built from it. |
| `OAUTH_REDIRECT_URI` | not set | A full redirect URI override for installations behind a proxy that serves the API under a path. Must end in `/api/oauth/callback`. |
| `OAUTH_INTERACTIVE_GRANTS_ENABLED` | on | `false` removes browser and device sign-in. |
| `OAUTH_TOKEN_CACHE_ENABLED` | on | `false` fetches a new token for every request. |
| `JWT_SECRET` | — | Must be set; browser sign-in is refused without it. |

