Product documentation
Updated September 27, 2026

Sign In With Steps: Login, One-Time Code, Token and Refresh

Some APIs sign you in with a sequence of calls they designed themselves. A typical example: post a username and password, receive a one-time code by SMS or email, post the code back, and receive a bearer token plus a refresh token.

View as Markdown

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

Overview

Some APIs sign you in with a sequence of calls they designed themselves. A typical example: post a username and password, receive a one-time code by SMS or email, post the code back, and receive a bearer token plus a refresh token. Later, a refresh call of the API's own design renews the token. No single-request method, and not standard OAuth 2.0, can do that.

The Sign in with steps method in Shift-Left Studio can. You describe each request once. You type the one-time code once per sign-in, not on every run. After that, the server keeps the sign-in alive with your refresh request, so scheduled runs, packs, CI runs and the local runner (Agent) keep working until the API's refresh token itself runs out.

Key concepts

TermMeaning
StepOne request in the sign-in, in order. A sign-in can have up to 5 steps.
Keep from the responseA value a step saves for later steps, such as a verification token.
Code stepThe one step that waits for a person to type a one-time code.
TemplateA placeholder in a URL or body, filled in when the step runs.
GrantThe result of a sign-in: the token, the refresh token, and their expiry times. The server holds it.
Machine flowA sign-in with no code step (or with a fixed test code). Runs sign in by themselves.

Templates you can use

TemplateFilled withWhere it can be used
{{credentials.username}}, {{credentials.password}}The profile's Username and PasswordAny step
{{step.<name>}}A value an earlier step keptLater steps only
{{input.<name>}}The code a person typesThe code step only
{{grant.accessToken}}, {{grant.refreshToken}}The current tokensThe refresh request only
{{MY_VARIABLE}}A project variableAnywhere

When to use it

Your API signs in with…Use
Login → one-time code → token (+ refresh)Sign in with steps
Login → token, no codeSign in with steps (no code step); runs sign in by themselves
Standard OAuth 2.0 (Entra ID, Okta, Auth0, Keycloak, Google)OAuth 2.0 Sign-In
One request that exchanges an API key for a tokenAPI Key + Bearer Token
A login that sets a session cookieCookie Login Flow

Before you begin

  • Know each sign-in request: its URL, method, body, and which response fields hold the values you need.
  • Use a dedicated test account. Every run acts as this user.
  • Have access to the account's one-time codes (phone or mailbox) when you sign in.

Step 1 — Create the profile and pick a starting point

  1. Go to Project Settings → Authentication Profiles and click New Profile.
  2. Enter a Profile Name and click Next.
  3. Under Authentication Method, select Sign in with steps.
  4. In Start from, choose a starting point:
    • Log in → one-time code → token (renews itself): two steps, the token locations and a refresh request. Only the URLs are left for you.
    • Log in → token (no code): one login step.
    • Start from a blank step.

Step 2 — Enter the credentials

  1. Enter the Username.
  2. Enter the Password. It is stored encrypted.

Important: Never type the password into a step body. Write {{credentials.password}} instead. The profile will not save if a body contains the password itself, because step bodies are not encrypted.

Step 3 — Build each step

For each step card (Step 1, Step 2, …):

  1. Give the step a name, choose the HTTP method, and enter the URL.
  2. For anything other than GET, choose the Body type (JSON or Form (x-www-form-urlencoded)) and write the Body using templates. The hint under the box lists the templates available to that step.
  3. Under Keep from the response (for later steps), click Keep a value for each value a later step needs. Enter the name to save it as, choose Response body, Response header or Cookie, and enter the path (for example verifyOTPToken or data.token).
  4. On the step that sends the one-time code, select Needs a code from a person (sent by SMS, email or an app). Then set:
    • Called: the code's name. The body uses {{input.otp}} if it is called otp.
    • Shown as: the label on the code box, for example One-time code.
    • Code expires at (saved value): a value an earlier step kept that holds the code's expiry. The code box then shows a countdown.
  5. Use the arrow icons to reorder steps, the bin icon to remove one, and Add a step to add another.

Only one step can wait for a code. Problems are shown in red under the step, for example "Step 2 uses {{step.verifyOTPToken}}, but no earlier step saves "verifyOTPToken"."

Step 4 — Say where the token is

Under The token, from the last step's response:

FieldWhat to enter
Token is atJSON path to the token, for example bearerToken. Required.
It expires atPath to its expiry: an ISO date with a time zone, an epoch time, or seconds. Optional.
Refresh token is atPath to the refresh token.
Refresh token expires atPath to the refresh token's expiry.
Send it in the headerDefault Authorization.
With the prefixUsually Bearer. Leave empty to send the token alone.

How Studio decides when the token expires: a date with a time zone (Z or +03:00) is read exactly, including seven-digit fractional seconds. A date without a time zone cannot be read reliably, so Studio uses a configured lifetime instead (default one hour). When the response states nothing, Studio reads a JWT's own expiry, and otherwise uses that default.

Step 5 — Add the refresh request

  1. Select Renew the token with a refresh request.
  2. Choose the method and enter the refresh URL.
  3. Write the Body, for example {"accessToken": "{{grant.accessToken}}", "refreshToken": "{{grant.refreshToken}}"}.

The access token is sent even when it has expired, because many refresh endpoints require it. The refresh request cannot use {{step.…}} or {{input.…}} values; it runs long after sign-in. Without a refresh request, a person must sign in again whenever the token runs out.

Step 6 — Save and sign in

  1. Continue to Save Profile, or click Sign in in the Sign-in panel (Studio saves the profile first).
  2. Studio runs the steps up to the code step, and a code box appears with a countdown, for example The code expires in 4:32.
  3. Type the code you received and click Submit.
  4. The panel shows, for example, Signed in as jane. Token valid until 23 Sep 2026, 11:55. It renews itself until 7 Oct 2026, 10:55.
  5. Activate the profile for its environment if it is not already active.

About the code box:

  • The code is sent once and never stored. It is masked in the audit log.
  • A mistyped code can be retyped up to three times (your API may allow fewer). After that, the sign-in starts again.
  • An expired code is refused before it is sent to your API, because many APIs lock an account after a failed or reused code. The box says The code has expired. Start the sign-in again for a new one.
  • Changing a step's URL or body, or the username, while a code is waiting cancels that sign-in.

Worked example

This API signs in with three calls:

POST /API/Auth/Login           {"username", "password"}
  → {"username", "verifyOTPToken", "otpExpiresOn": "2026-09-23T10:55:43.4709579+03:00"}

POST /API/Auth/VerifyLoginOTP  {"username", "otp", "verifyOTPToken"}
  → {"bearerToken", "bearerTokenExpiresOn", "refreshToken", "refreshTokenExpiresOn", "message"}

POST /API/Auth/RefreshToken    {"accessToken", "refreshToken"}
SettingValue
Step 1 URLhttps://<host>/API/Auth/Login
Step 1 Body{"username": "{{credentials.username}}", "password": "{{credentials.password}}"}
Step 1 keepsverifyOTPToken ← verifyOTPToken; otpExpiresOn ← otpExpiresOn
Step 2 URLhttps://<host>/API/Auth/VerifyLoginOTP
Step 2 Body{"username": "{{credentials.username}}", "otp": "{{input.otp}}", "verifyOTPToken": "{{step.verifyOTPToken}}"}
Step 2Needs a code from a person, Called otp, Code expires at otpExpiresOn
Token is at / It expires atbearerToken / bearerTokenExpiresOn
Refresh token is at / Refresh token expires atrefreshToken / refreshTokenExpiresOn
RefreshPOST https://<host>/API/Auth/RefreshToken, body {"accessToken": "{{grant.accessToken}}", "refreshToken": "{{grant.refreshToken}}"}

Every test request then carries Authorization: Bearer <bearerToken>. The Log in → one-time code → token (renews itself) starting point fills in all of this except the three URLs, and Load Sample Configuration fills in a complete example.

A test server with a fixed code

If your staging server accepts the same code every time, enter it under Fixed code for a test server (optional). It is stored encrypted. No step then needs a person: the first run signs in by itself, and if a renewal is refused, the run signs in again. Use this only on test servers, because it turns off the protection the code provides.

What happens on a run

SituationWhat the run does
Token validSends it.
Token expires within 60 seconds and a refresh request existsRenews once, even when many tests ask at the same moment, stores the new tokens, and sends the new token.
The API refuses the renewalMarks the sign-in expired. Tests fail with "sign in again", and run analysis names one cause: The sign-in for "<profile name>" has expired, not a list of test failures.
The renewal fails for a temporary reason (network, 5xx)Uses the current token while it is still valid; otherwise fails with a retryable error.
No refresh request and the token has expiredA person signs in again.
Machine flow (no code step, or a fixed code)Signs in by itself whenever it needs to.

The local runner (Agent) never runs the steps and never receives the password, the refresh token, the steps or a fixed code. It receives a live access token and asks the server for a new one when it runs low. Performance runs read authentication again while they run, so a long soak test keeps a valid token.

When the refresh token is within two days of running out, the panel warns: "The renewal token runs out on … Sign in again before then so scheduled runs keep working."

Editing a signed-in profile

Changing a label, the password or the refresh request keeps the sign-in. Changing what decides which sign-in it is (a step's method, URL, body or headers, the username, or where the token is and how it is sent) drops it, and the dialog tells you to sign in again. A duplicated profile never shares the original's sign-in. Sign out removes the stored tokens.

Configure it with the Project Assistant

You can ask the Project Assistant to set up a Sign in with steps profile from a description of your API's sign-in calls. Because bodies are templates, it can set up the whole flow: URLs, bodies, saved values, token paths and the refresh request. It never accepts the password or a fixed code in chat, because a secret typed into a conversation stays in the transcript; it opens the Authentication screen with the Password field marked instead. It can also start the sign-in, but you type the one-time code on the Authentication screen, not in the chat. The assistant's card completes by itself when you submit the code.

Troubleshooting

SymptomWhy it happensWhat to do
The profile will not save and mentions the passwordA step body contains the literal password.Replace it with {{credentials.password}}.
"Say where the token is in the last step's response."Token is at is empty.Enter the JSON path to the token.
"Step N uses {{step.x}}, but no earlier step saves "x"."A template refers to a value no earlier step keeps.Add Keep a value on an earlier step, or fix the name.
"The code was not accepted."The code was wrong, reused, or expired at the API.Retype it (up to three tries), or start the sign-in again.
The countdown never appearsCode expires at (saved value) is not set.Keep the expiry field from the login step and select it.
Token expires sooner than expected, or "(the API did not say — assumed)" appearsThe response gives no expiry, or a date without a time zone.Point It expires at at a field with a time zone, or at seconds remaining.
Runs fail with "The sign-in for … has expired"The API refused the refresh, or the refresh token ran out.Click Sign in again.
An error names a step and a statusThat step's request failed. Errors show the status and the API's own message, never the response body.Check that step's URL, method and body.

Best practices

  • Start from the Log in → one-time code → token (renews itself) starting point and change only what differs.
  • Always add a refresh request if the API has one; otherwise every token expiry needs a person.
  • Keep the code's expiry so the countdown tells you how long you have.
  • Use a fixed code only on test servers, never on an environment with real users.
  • Sign in again before the refresh token runs out if you rely on scheduled runs.

Limitations

  • Up to 5 steps, and only one step can wait for a code.
  • The token is sent in a header only (default Authorization: Bearer …). There is no query-parameter delivery.
  • Step bodies are JSON or form-encoded.
  • Values kept by earlier steps are held on the server, encrypted, for at most 15 minutes during a sign-in, and never sent to the browser.
  • The sign-in requests go from the server to the URLs you configure, so anyone who can edit the project's authentication chooses where they go.

FAQ

How often do I type the code? Once per sign-in. With a refresh request, runs renew the token until the API's refresh token expires.

Can scheduled and CI runs use this profile? Yes. The server holds the sign-in, so no one needs to be present.

Is the password ever sent to the Agent? No. The Agent only receives a live access token.

What if my API needs no code? Choose Log in → token (no code). Runs then sign in by themselves.

For administrators (self-hosted installations)

SettingDefaultWhat switching it does
SIGN_IN_FLOW_AUTH_ENABLEDonfalse removes the method. Existing profiles then refuse to authenticate with a message that says so; they never send requests without authentication.

Next steps

Still stuck?

Tell us what you’re trying to accomplish and we’ll point you to the right setup—installation, auth, or CI/CD wiring.