{"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"customize-the-auth-gate-in-replay","__idx":0},"children":["Customize the auth gate in Replay"]},{"$$mdtype":"Tag","name":"ConfigOptionRequirements","attributes":{"products":["Revel","Realm"],"plans":["Pro","Enterprise","Enterprise+"]},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," component wraps the entire Replay UI and controls whether users can reach it."," ","By ejecting and replacing it, you can require users to authenticate before Replay opens."," ","Examples include completing an OAuth 2.0 flow, entering an API key, or signing in with your own identity provider."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a user authenticates, call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," to set the token as an environment variable."," ","Replay picks up the value automatically and prefills it in every operation that declares a matching security scheme."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"before-you-begin","__idx":1},"children":["Before you begin"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Make sure you have the following:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["a basic understanding of TypeScript and React hooks"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["an OpenAPI description file with at least one security scheme defined"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["credentials for the identity provider you want to integrate (client ID, authorization endpoint, and so on)"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"eject-replaygate","__idx":2},"children":["Eject ReplayGate"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To customize the auth gate, first eject the component:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli eject component 'ReplayGate/ReplayGate.tsx'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This command creates a local copy of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate.tsx"]}," in your project's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@theme/components/ReplayGate/"]}," folder."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"props-reference","__idx":3},"children":["Props reference"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prop"},"children":["Prop"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(envName: string, values: Record<string, string | { value: string, isSecret?: boolean }>) => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["REQUIRED."]}," ","Injects key-value pairs into the named environment."," ","Call this after a successful auth to set the token."," ","A plain string sets the value; pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ value, isSecret }"]}," to also control whether Replay masks it."," ","If no environment has this name, Replay creates a new environment."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setSelectedEnvironment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(envName: string) => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["REQUIRED."]}," ","Switches Replay's active environment."," ","Use this when you want to automatically select the environment that received the token."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["selectedEnvironment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The name of the currently active environment, passed down from Replay settings."," ","Use as the target for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," when no specific environment name is hardcoded."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The current OpenAPI description's id, when your project defines one."," ","Use it to vary behavior per API in a multi-API catalog."," ","For example, scope a stored token's key so different APIs don't share (or overwrite) each other's credentials."," ","You can also use a different auth mechanism entirely for APIs that need one."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onClose"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["() => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["REQUIRED."]}," ","Closes the Replay overlay."," ","Call it when a user dismisses the gate."," ","A dismissed gate that renders ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["children"]}," instead leaves Replay reachable without authentication."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["children"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["React.ReactNode"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["REQUIRED."]}," ","The Replay UI."," ","Render this when authentication succeeds."," ","You can also mount it hidden while auth is in progress to allow Replay's store to initialize in the background."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-environment-values-work","__idx":4},"children":["How environment values work"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Replay generates one environment per API server defined in your OpenAPI description."," ","The environment name is the server's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["description"]},", or URL — whichever is set first."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each environment has a set of inputs derived from the operation's security schemes."," ","The input names follow the pattern ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{schemeId}{suffix}"]},"."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schemeId"]}," is the security scheme identifier from your OpenAPI description, and the suffix depends on the scheme type:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Scheme type"},"children":["Scheme type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Suffix"},"children":["Suffix"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Example input name"},"children":["Example input name"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http"]}," (bearer / JWT)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["_token"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bearerAuth_token"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oauth2"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["_token"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["myOAuth_token"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiKey"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(none)"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiKey"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http"]}," (basic, digest) — username"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["_username"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["basicAuth_username"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http"]}," (basic, digest) — password"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["_password"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["basicAuth_password"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass the input name as the key when calling ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"setEnvironmentValues(selectedEnvironment, { bearerAuth_token: token });\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Replay resolves this value at request time and injects it into the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization"]}," header automatically."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-an-environment","__idx":5},"children":["Create an environment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create new environments when your gate issues credentials that don't belong to any environment declared in your OpenAPI description."," ","For example, a per-user sandbox obtained at sign-in."," ","Replay creates that environment instead of ignoring the call."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To create a new environment:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," with a name that doesn't match any environment in your OpenAPI description."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Creating an environment doesn't select it."," ","Call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setSelectedEnvironment"]}," to make it active:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"setEnvironmentValues('Authorized', { bearerAuth_token: { value: token, isSecret: true } });\nsetSelectedEnvironment('Authorized');\n","lang":"tsx"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The new environment takes its server from the environment selected by the page."," ","Requests reach the same host the page targets."," ","The first server in your OpenAPI description is the fallback."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Only the names your gate passes to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," create environments."," ","Environment values from the project configuration can't do that, so a name that doesn't match an existing environment is silently dropped."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Environments created this way are exempt from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedEnvironments"]}," setting, because the gate requested them explicitly."," ","Users can't rename or delete these environments from the environments panel, as your gate recreates them on the next page load."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In projects that set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedEnvironments"]},", values users type into created environments are not restored on page reload."," ","Your gate re-injects its own values."," ","The credentials it manages are unaffected."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"custom-input-names-and-masking","__idx":6},"children":["Custom input names and masking"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A key passed to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," does not have to match a name generated from the OpenAPI description."," ","If the key matches an existing input (a security scheme, path parameter, or dynamic value), Replay overwrites that input's value."," ","If it doesn't match anything, Replay creates a new input with that name in the target environment."," ","This behavior is useful for injecting values your OpenAPI description doesn't model as security, such as an internal tracing header."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["By default, a value is not masked in the Replay UI."," ","A plain string keeps an existing input's current masking, and a newly created input starts out unmasked."," ","Pass an object instead of a string to control this explicitly:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","header":{"controls":{"copy":{}}},"source":"// Plain string: sets the value, keeps whatever masking the input already had\n// (or unmasked, if this creates a new input).\nsetEnvironmentValues(selectedEnvironment, { bearerAuth_token: token });\n\n// { value, isSecret }: also sets whether Replay masks the value.\nsetEnvironmentValues(selectedEnvironment, {\n  bearerAuth_token: { value: token, isSecret: true },\n});\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"default-behavior","__idx":7},"children":["Default behavior"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," passes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["children"]}," through without any auth check — all users can access Replay immediately."," ","If that is acceptable for your use case, you do not need to eject this component."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","data-title":"@theme/components/ReplayGate/ReplayGate.tsx (no-op)","header":{"title":"@theme/components/ReplayGate/ReplayGate.tsx (no-op)","controls":{"copy":{}}},"source":"import React from 'react';\nimport type { ReplayGateProps } from '@redocly/theme/components/ReplayGate/ReplayGate';\n\nexport function ReplayGate({ children }: ReplayGateProps) {\n  return <>{children}</>;\n}\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"example-oauth-20-popup-flow","__idx":8},"children":["Example: OAuth 2.0 popup flow"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following example implements a full OAuth 2.0 authorization code flow using a popup window."," ","It validates any stored token on mount and opens the authorization URL automatically if the token is missing or expired."," ","After a successful sign-in, it injects the token into Replay's active environment."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["While that initial check runs, it shows a loading spinner instead of the sign-in button."," ","Without a distinct loading state, returning users with a valid token would see a flash."," ","The sign-in button would show for however long the validation request takes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The panel is dismissible: a close button and a click on the backdrop call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onClose"]},", which closes the Replay overlay."," ","Dismissing never falls through to Replay, so a successful sign-in stays the only way past the gate."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Replace the constants at the top (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CLIENT_ID"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAUTH_BASE_URL"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["REDIRECT_URI"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TOKEN_INPUT_NAME"]},") with values from your own identity provider."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"localStorage and security"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This example stores the access token in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["localStorage"]}," so users stay signed in across page reloads."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["localStorage"]}," persists indefinitely and is readable by any script on the page, making it vulnerable to XSS."," ","For sensitive tokens, prefer ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionStorage"]},", which is cleared when the browser session ends."]}]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"ReplayGate.tsx","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","data-title":"@theme/components/ReplayGate/ReplayGate.tsx","header":{"title":"@theme/components/ReplayGate/ReplayGate.tsx","controls":{"copy":{}}},"source":"import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport styled, { keyframes } from 'styled-components';\nimport { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';\nimport type { ReplayGateProps } from '@redocly/theme/components/ReplayGate/ReplayGate';\n\n// Replace with your OAuth 2.0 provider's details.\nconst CLIENT_ID        = 'YOUR_CLIENT_ID';\nconst OAUTH_BASE_URL   = 'https://auth.example.com/oauth2';\nconst USERINFO_URL     = 'https://auth.example.com/oidc/userinfo';\nconst REDIRECT_URI     = 'https://your-portal.example.com/api/login';\nconst STORAGE_KEY      = 'replay_access_token';\n// Input name: OpenAPI security scheme id + suffix.\n// For a scheme id 'bearerAuth' with type http/bearer, the suffix is '_token'.\nconst TOKEN_INPUT_NAME = 'bearerAuth_token';\n// Used when the page selects no environment. Replay creates an environment for an unknown name.\nconst FALLBACK_ENV_NAME = 'Authorized';\n\n// Pre-check: avoids a network call for clearly-expired JWTs.\nfunction isJwtExpired(token: string): boolean {\n  try {\n    const b64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');\n    const { exp } = JSON.parse(atob(b64)) as { exp?: number };\n    return typeof exp === 'number' && exp * 1000 <= Date.now();\n  } catch {\n    return false;\n  }\n}\n\n// Validates the token against the OIDC UserInfo endpoint.\n// Returns false if expired, revoked, or the network request fails.\nasync function isTokenValid(token: string): Promise<boolean> {\n  if (isJwtExpired(token)) return false;\n  try {\n    const res = await fetch(USERINFO_URL, {\n      headers: { Authorization: `Bearer ${token}` },\n    });\n    return res.ok;\n  } catch {\n    return false;\n  }\n}\n\nconst slideIn = keyframes`\n  from { transform: translateX(100%); }\n  to   { transform: translateX(0); }\n`;\n\nconst spin = keyframes`\n  to { transform: rotate(360deg); }\n`;\n\nconst Spinner = styled.div`\n  width: 32px;\n  height: 32px;\n  border: 3px solid var(--text-color-secondary, #888);\n  border-bottom-color: transparent;\n  border-radius: 50%;\n  animation: ${spin} 0.8s linear infinite;\n`;\n\nconst OverlayWrapper = styled.div`\n  position: fixed;\n  inset: 0;\n  background: rgba(0, 0, 0, 0.35);\n  display: flex;\n  justify-content: flex-end;\n  z-index: var(--z-index-overlay);\n`;\n\nconst Panel = styled.div`\n  width: 440px;\n  height: 100%;\n  background: var(--bg-color, #fff);\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: 48px 36px;\n  gap: 20px;\n  box-shadow: -4px 0 32px rgba(0, 0, 0, 0.14);\n  animation: ${slideIn} 0.3s ease-out;\n`;\n\n// Matches Replay's own overlay close button, which sits on the backdrop next to the panel.\nconst CloseButton = styled.button`\n  position: relative;\n  right: var(--spacing-sm);\n  top: var(--spacing-sm);\n  align-self: flex-start;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 24px;\n  height: 24px;\n  padding: 0;\n  border: none;\n  border-radius: var(--border-radius-md);\n  background-color: var(--button-bg-color-secondary);\n  cursor: pointer;\n`;\n\nconst CloseButtonIcon = styled(CloseIcon)`\n  width: 14px;\n  height: 14px;\n`;\n\n// Always mounted at the same tree position across 'checking'/'authorizing'/'ready' so\n// React never unmounts+remounts `children` — only its display toggles. Not rendered at\n// all during 'gate'.\nconst ChildrenWrapper = styled.div<{ $hidden: boolean }>`\n  display: ${({ $hidden }) => ($hidden ? 'none' : 'contents')};\n`;\n\ntype Phase = 'checking' | 'gate' | 'authorizing' | 'ready';\n\nexport function ReplayGate({\n  setEnvironmentValues,\n  setSelectedEnvironment,\n  selectedEnvironment,\n  apiId,\n  onClose,\n  children,\n}: ReplayGateProps) {\n  // Starts at 'checking', not 'gate' — otherwise the sign-in button flashes on\n  // screen for returning users while the stored token is still being validated.\n  const [phase, setPhase] = useState<Phase>('checking');\n  const [authUrl, setAuthUrl] = useState('');\n  const popupRef = useRef<Window | null>(null);\n  const channelRef = useRef<BroadcastChannel | null>(null);\n  // Scope the stored token per API so different APIs in the same catalog don't\n  // share (or overwrite) each other's credentials.\n  const storageKey = apiId ? `${STORAGE_KEY}_${apiId}` : STORAGE_KEY;\n\n  const startAuth = useCallback(() => {\n    const state = crypto.randomUUID();\n    const url =\n      `${OAUTH_BASE_URL}/auth` +\n      `?client_id=${encodeURIComponent(CLIENT_ID)}` +\n      `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +\n      `&response_type=code&scope=openid+email+offline&state=${state}`;\n\n    setAuthUrl(url);\n    popupRef.current = window.open(url, '_blank');\n    setPhase('authorizing');\n\n    channelRef.current?.close();\n    const channel = new BroadcastChannel('replay-auth');\n    channelRef.current = channel;\n\n    channel.onmessage = (event: MessageEvent) => {\n      if (event.data?.type !== 'REPLAY_AUTH_DONE' || event.data.state !== state) return;\n      channel.close();\n      channelRef.current = null;\n      popupRef.current?.close();\n      popupRef.current = null;\n\n      const token: string = event.data.access_token;\n      localStorage.setItem(storageKey, token);\n\n      // When the page selects no environment, name one anyway: Replay creates it on demand.\n      const targetEnvironment = selectedEnvironment || FALLBACK_ENV_NAME;\n\n      setEnvironmentValues(targetEnvironment, {\n        [TOKEN_INPUT_NAME]: { value: token, isSecret: true },\n      });\n      setSelectedEnvironment(targetEnvironment);\n      setPhase('ready');\n    };\n  }, [setEnvironmentValues, setSelectedEnvironment, selectedEnvironment, storageKey]);\n\n  // On mount: restore a valid stored token, or start auth automatically.\n  useEffect(() => {\n    (async () => {\n      const stored = localStorage.getItem(storageKey);\n      if (stored && (await isTokenValid(stored))) {\n        // When the page selects no environment, name one anyway: Replay creates it on demand.\n        const targetEnvironment = selectedEnvironment || FALLBACK_ENV_NAME;\n\n        setEnvironmentValues(targetEnvironment, {\n          [TOKEN_INPUT_NAME]: { value: stored, isSecret: true },\n        });\n        setSelectedEnvironment(targetEnvironment);\n        setPhase('ready');\n      } else {\n        localStorage.removeItem(storageKey);\n        setPhase('gate');\n      }\n    })();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    return () => { channelRef.current?.close(); };\n  }, []);\n\n  return (\n    <>\n      {phase !== 'gate' && (\n        <ChildrenWrapper $hidden={phase !== 'ready'}>{children}</ChildrenWrapper>\n      )}\n\n      {phase !== 'ready' && (\n        <OverlayWrapper\n          onClick={(event) => {\n            // A click on the backdrop itself closes Replay; clicks inside the panel don't.\n            if (event.target === event.currentTarget) onClose();\n          }}\n        >\n          <CloseButton aria-label=\"Close\" onClick={onClose}>\n            <CloseButtonIcon />\n          </CloseButton>\n\n          <Panel>\n            {phase === 'checking' && <Spinner />}\n\n            {phase === 'gate' && (\n              <>\n                <h2>Sign in to try it</h2>\n                <p>Sign in to send live API requests.</p>\n                <button onClick={startAuth}>Sign in</button>\n              </>\n            )}\n            {phase === 'authorizing' && (\n              <>\n                <h2>Complete sign-in</h2>\n                <p>A sign-in window has opened. Complete sign-in to continue.</p>\n                <button\n                  onClick={() => {\n                    if (popupRef.current && !popupRef.current.closed) {\n                      popupRef.current.focus();\n                    } else {\n                      popupRef.current = window.open(authUrl, 'auth-popup', 'width=520,height=680');\n                    }\n                  }}\n                >\n                  Re-open sign-in window\n                </button>\n              </>\n            )}\n          </Panel>\n        </OverlayWrapper>\n      )}\n    </>\n  );\n}\n","lang":"tsx"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Callback page","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The popup redirects to your redirect URI after the user signs in."," ","An ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization/api-functions/api-functions-reference"},"children":["API function"]}," exchanges the authorization code for an access token."," ","It then returns an HTML page that delivers the token back to the opener via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BroadcastChannel"]}," and closes the popup."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The filename's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".get"]}," suffix maps this file to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /api/login"]},", matching the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["REDIRECT_URI"]}," used above."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","data-title":"@api/login.get.ts","header":{"title":"@api/login.get.ts","controls":{"copy":{}}},"source":"const CLIENT_ID    = 'YOUR_CLIENT_ID';\nconst REDIRECT_URI = 'https://your-portal.example.com/api/login';\nconst TOKEN_URL    = 'https://auth.example.com/oauth2/token';\n\nexport default async function (request: Request) {\n  const url = new URL(request.url);\n  const code = url.searchParams.get('code');\n  const state = url.searchParams.get('state');\n  const clientSecret = process.env.OAUTH_CLIENT_SECRET;\n\n  if (!code || !clientSecret) {\n    return htmlResponse(!clientSecret ? 'Not configured' : 'Missing code', null, null);\n  }\n\n  try {\n    const res = await fetch(TOKEN_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: new URLSearchParams({\n        grant_type: 'authorization_code',\n        code,\n        redirect_uri: REDIRECT_URI,\n        client_id: CLIENT_ID,\n        client_secret: clientSecret,\n      }),\n    });\n\n    if (!res.ok) return htmlResponse('Token exchange failed', null, null);\n    const { access_token } = await res.json() as { access_token: string };\n    return htmlResponse(null, access_token, state);\n  } catch (err) {\n    return htmlResponse(String(err), null, null);\n  }\n}\n\n// Serializes a value for the inline script. `state` comes from the query string, so escape\n// `<`: a value containing `</script>` would otherwise close the script block and inject markup.\nfunction jsValue(value: unknown) {\n  return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n// Delivers the token back to the popup's opener, then closes the popup.\nfunction htmlResponse(error: string | null, accessToken: string | null, state: string | null) {\n  const script = error\n    ? `document.getElementById('msg').textContent = ${jsValue('Sign-in failed: ' + error)};`\n    : `var channel = new BroadcastChannel('replay-auth');\n       channel.postMessage({ type: 'REPLAY_AUTH_DONE', access_token: ${jsValue(accessToken)}, state: ${jsValue(state)} });\n       channel.close();\n       window.close();`;\n\n  const html = `<!DOCTYPE html>\n<html>\n  <head><meta charset=\"utf-8\"><title>Completing sign-in…</title></head>\n  <body style=\"font-family:sans-serif;padding:32px\">\n    <p id=\"msg\">Completing sign-in, this window will close…</p>\n    <script>${script}</script>\n  </body>\n</html>`;\n\n  return new Response(html, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });\n}\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Keep your client secret out of source control"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Never commit the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAUTH_CLIENT_SECRET"]}," environment variable to your repository."," ","Set it in your deployment environment or a local ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," file that is excluded by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".gitignore"]},"."]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"how-the-popup-flow-works","__idx":9},"children":["How the popup flow works"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," opens the authorization URL in a popup window."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The user signs in and the identity provider redirects the popup to your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["REDIRECT_URI"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@api/login.get.ts"]}," function exchanges the authorization code for an access token and returns an HTML page."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["That page posts the token to the opener via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BroadcastChannel"]}," and closes the popup."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," receives the token, calls ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setSelectedEnvironment"]},", and renders ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["children"]},"."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Always call setSelectedEnvironment"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Replay's active environment does not switch on its own."," ","Without a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setSelectedEnvironment"]}," call, the token lands in the target environment's inputs, but Replay keeps using whichever environment was already active."," ","The token never appears to take effect."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"eject-replaytopbaractions","__idx":10},"children":["Eject ReplayTopBarActions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayTopBarActions"]}," renders custom UI in Replay's top bar, next to the environment switcher."," ","The default implementation renders nothing."," ","Eject it to add a sign-out button, a multi-account switcher, or an account indicator."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli eject component 'ReplayTopBarActions/ReplayTopBarActions.tsx'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"props-reference-1","__idx":11},"children":["Props reference"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prop"},"children":["Prop"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["environments"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReadonlyArray<{ name: string }>"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The list of available environments from the API servers in your OpenAPI description."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["activeEnvironment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The name of the currently active environment."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setEnvironmentValues"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(envName: string, values: Record<string, string | { value: string, isSecret?: boolean }>) => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Injects key-value pairs into the named environment."," ","Call with an empty string to clear a value on sign-out."," ","A plain string sets the value; pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ value, isSecret }"]}," to also control whether Replay masks it."," ","Unlike the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," prop of the same name, this one only updates environments that already exist; it does not create them."," ","See ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"#custom-input-names-and-masking"},"children":["Custom input names and masking"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setSelectedEnvironment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(envName: string) => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Switches Replay's active environment."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The current OpenAPI description's id, when your project defines one."," ","Pass the same value your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," uses so sign-out clears the same scoped storage key."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"example-sign-out-button","__idx":12},"children":["Example: sign-out button"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","data-title":"@theme/components/ReplayTopBarActions/ReplayTopBarActions.tsx","header":{"title":"@theme/components/ReplayTopBarActions/ReplayTopBarActions.tsx","controls":{"copy":{}}},"source":"import React from 'react';\nimport type { ReplayTopBarActionsProps } from '@redocly/theme/components/ReplayTopBarActions/ReplayTopBarActions';\n\nconst STORAGE_KEY     = 'replay_access_token';\nconst TOKEN_INPUT_NAME = 'bearerAuth_token';\n\nexport function ReplayTopBarActions({\n  environments,\n  setEnvironmentValues,\n  apiId,\n}: ReplayTopBarActionsProps) {\n  const handleSignOut = () => {\n    localStorage.removeItem(apiId ? `${STORAGE_KEY}_${apiId}` : STORAGE_KEY);\n    for (const env of environments) {\n      setEnvironmentValues(env.name, { [TOKEN_INPUT_NAME]: '' });\n    }\n    window.location.reload();\n  };\n\n  return (\n    <button onClick={handleSignOut} style={{ padding: '6px 12px' }}>\n      Sign out\n    </button>\n  );\n}\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Passing an empty string (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["''"]},") for a token input clears the value in Replay's environment store — the same as a user manually deleting the field."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"eject-replaysecurityschemeactions","__idx":13},"children":["Eject ReplaySecuritySchemeActions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplaySecuritySchemeActions"]}," renders inside each security scheme card in Replay's ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Security"]}," tab."," ","The default implementation renders nothing."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can offer users a way to supply a credential inside security scheme cards in Replay's ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Security"]}," tab."," ","Eject the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplaySecuritySchemeActions"]}," component and configure it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For example, add a sign-in button for your identity provider."," ","Alternatively, you can add a link to the page that issues API keys, or a note naming the credential the scheme takes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Unlike ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]},", it doesn't restrict access."," ","Users reach every operation as usual, and the card offers a way to fill the credential it needs."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Replay renders the component only for schemes that take a single credential."," ","Basic and digest authentication take a username and a password, so their cards render no slot."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli eject component 'ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.tsx'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"choose-where-the-actions-appear","__idx":14},"children":["Choose where the actions appear"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your component receives every card, decides where to act, and returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," everywhere else."," ","Narrow the offer by any of the following, and combine them as you need."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Target"},"children":["Target"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prop"},"children":["Prop"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Example"},"children":["Example"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A single API in a catalog"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Offer a token for the payments API, and leave every other API alone."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A single operation"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Offer a token for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["listPushes"]},", and leave the other operations alone."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A single server"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["environment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Ask for a credential on production, and skip the mock server, which needs none."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A type of scheme"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["securityType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Offer a sign-in for OAuth 2.0 cards, and a key generator for API key cards."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["One named scheme"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schemeId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Act on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PartnerKey"]}," only, where an API documents several API key schemes."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"props-reference-2","__idx":15},"children":["Props reference"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Prop"},"children":["Prop"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schemeId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["string | null"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The security scheme id from your OpenAPI description."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," when a user picks an authorization type by hand."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAuth2"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["securityType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'oauth2' | 'openIdConnect' | 'apiKey' | 'http-bearer'"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["What the card asks for, named after the fields in your API description."," ","Import ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplaySecurityType"]}," and annotate what you match against, so a misspelled type fails to compile."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oauth2"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http-bearer"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["environment"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The name of the currently active environment, which is the server a request goes to."," ","Use it to decide whether this server needs a credential, and which provider issues it."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Production server"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Mock server"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string | undefined"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}," of the operation the card belongs to."," ","Use it to offer a credential for some operations only."," ","Undefined when the description names no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]},", and in the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Workflows"]}," tab, which has no operation."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["listPushes"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["value"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["What the scheme's credential field resolves to, or an empty string."," ","An environment input counts only when the scheme's field references it."," ","Compare it with the credential you issued to tell your own token from one a user pasted."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["''"]}," for an empty field, the access token once one lands in it."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setValue"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["(value: string) => void"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Fills the scheme's credential."," ","Pass an empty string to clear it."," ","See ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"#how-environment-values-work"},"children":["How environment values work"]}," for where the value lands."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setValue(accessToken)"]}," after a sign-in, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setValue('')"]}," on a sign-out."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string | undefined"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The current OpenAPI description's id, when your project defines one."," ","Pass the same value your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," uses to keep behavior consistent per API."," ","Example: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payments-api"]},"."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"example-sign-in-offer","__idx":16},"children":["Example: sign-in offer"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following example offers a sign-in button on OAuth 2.0 cards, and a sign-out button once the card holds a token."," ","It makes the offer for one API only, so the other APIs in a catalog keep their default cards."," ","This example reuses the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@api/login.get.ts"]}," callback page from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]}," example above, which posts the token back over ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BroadcastChannel"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"tsx","data-title":"@theme/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.tsx","header":{"title":"@theme/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions.tsx","controls":{"copy":{}}},"source":"import React from 'react';\nimport type {\n  ReplaySecurityType,\n  ReplaySecuritySchemeActionsProps,\n} from '@redocly/theme/components/ReplaySecuritySchemeActions/ReplaySecuritySchemeActions';\n\nconst AUTH_URL = 'https://auth.example.com/oauth2/auth?client_id=YOUR_CLIENT_ID&response_type=code';\nconst PAYMENTS_API_ID = 'payments-api';\n// Environment names come from the servers in your API description.\nconst LIVE_SERVER = 'Production server';\n// An access token fits these two scheme types and no others.\nconst TOKEN_TYPES = new Set<ReplaySecurityType>(['oauth2', 'openIdConnect']);\n\n// Remember the token this component issued, so a token a user pastes is left alone.\nconst ISSUED_TOKEN_KEY = 'replay-issued-token';\n\nexport function ReplaySecuritySchemeActions({\n  securityType,\n  environment,\n  value,\n  setValue,\n  apiId,\n}: ReplaySecuritySchemeActionsProps) {\n  const handleSignIn = () => {\n    const state = crypto.randomUUID();\n    const channel = new BroadcastChannel('replay-auth');\n\n    channel.onmessage = (event: MessageEvent) => {\n      if (event.data?.type !== 'REPLAY_AUTH_DONE' || event.data.state !== state) return;\n      channel.close();\n      sessionStorage.setItem(ISSUED_TOKEN_KEY, event.data.access_token);\n      setValue(event.data.access_token);\n    };\n\n    window.open(`${AUTH_URL}&state=${state}`, 'replay-auth', 'width=520,height=680');\n  };\n\n  if (apiId !== PAYMENTS_API_ID || environment !== LIVE_SERVER) {\n    return null;\n  }\n\n  if (!TOKEN_TYPES.has(securityType)) {\n    return null;\n  }\n\n  return value && value === sessionStorage.getItem(ISSUED_TOKEN_KEY) ? (\n    <button\n      onClick={() => {\n        sessionStorage.removeItem(ISSUED_TOKEN_KEY);\n        setValue('');\n      }}\n    >\n      Sign out\n    </button>\n  ) : (\n    <button onClick={handleSignIn}>Sign in</button>\n  );\n}\n","lang":"tsx"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Session storage outlives the card, which Replay unmounts when a user switches operation, and it carries the token across a page reload."," ","It also ends with the browser tab."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Returning ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," leaves the card exactly as Replay renders it by default."," ","A catalog with several APIs reaches this component for every one of them, so match on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["apiId"]}," when the credential belongs to a single API."," ","Narrowing by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}," or by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schemeId"]}," takes the same shape: one more check before the component returns anything."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"use-cases","__idx":17},"children":["Use cases"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Ejecting ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayGate"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplayTopBarActions"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReplaySecuritySchemeActions"]}," is useful for:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["SSO enforcement"]},": require users to sign in with your company's identity provider before using Replay"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Token pre-fill"]},": silently inject a stored token so users don't need to paste credentials manually"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Multi-environment auth"]},": select different environments or token scopes based on the signed-in user's role"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Session management"]},": add a sign-out button or session expiration indicator inside Replay"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Credential handoff"]},": offer sign-in on the security scheme card itself, so users fill a credential without leaving the operation"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"resources","__idx":18},"children":["Resources"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization/configure-dynamic-replay-values"},"children":["Configure dynamic API data for Replay"]}]}," - Fetch tokens and other request values dynamically using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["useConfigureReplay"]}," hook"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization/configure-request-values"},"children":["Configure request values"]}]}," - Set static request values for headers, parameters, and security details in your OpenAPI descriptions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization/api-functions/api-functions-reference"},"children":["API functions reference"]}]}," - Function signature, routing, context helpers, and access control"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization/eject-components"},"children":["Eject components"]}]}," - Learn to eject and customize built-in components"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/realm/customization"},"children":["Extend with code"]}]}," - Discover all customization options for your project"]}]}]},"frontmatter":{"products":["Revel","Realm"],"plans":["Pro","Enterprise","Enterprise+"]},"tagList":["admonition","configOptionRequirements","tab","table","tabs"],"title":"Customize the auth gate in Replay","lastModified":"2026-10-01T23:00:57.000Z"}