JWTSettings() — supabase Function Reference
Architecture documentation for the JWTSettings() function in jwt-settings.tsx from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx lines 66–639
const JWTSettings = () => {
const { ref: projectRef } = useParams()
const newJwtSecrets = useFlag('newJwtSecrets')
const [customToken, setCustomToken] = useState<string>('')
const [showCustomTokenInput, setShowCustomTokenInput] = useState(false)
const [isCreatingKey, setIsCreatingKey] = useState<boolean>(false)
const [isRegeneratingKey, setIsGeneratingKey] = useState<boolean>(false)
const { can: canReadJWTSecret } = useAsyncCheckPermissions(
PermissionAction.READ,
'field.jwt_secret'
)
const { can: canGenerateNewJWTSecret } = useAsyncCheckPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_job.projects.update_jwt'
)
const { can: canUpdateConfig } = useAsyncCheckPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)
const { data } = useJwtSecretUpdatingStatusQuery({ projectRef })
const { data: config, isError } = useProjectPostgrestConfigQuery({ projectRef })
const { mutateAsync: updateJwt, isPending: isSubmittingJwtSecretUpdateRequest } =
useJwtSecretUpdateMutation()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
const { data: legacyKey } = useLegacyJWTSigningKeyQuery(
{
projectRef,
},
{ enabled: canReadAPIKeys }
)
const { data: legacyAPIKeysStatus } = useLegacyAPIKeysStatusQuery(
{ projectRef },
{ enabled: canReadAPIKeys }
)
const { data: authConfig, isPending: isLoadingAuthConfig } = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig, isPending: isUpdatingAuthConfig } =
useAuthConfigUpdateMutation()
const { Failed, Updated, Updating } = JwtSecretUpdateStatus
const isJwtSecretUpdateFailed = data?.jwtSecretUpdateStatus === Failed
const isNotUpdatingJwtSecret =
data?.jwtSecretUpdateStatus === undefined || data?.jwtSecretUpdateStatus === Updated
const isUpdatingJwtSecret = data?.jwtSecretUpdateStatus === Updating
const jwtSecretUpdateErrorMessage =
JWT_SECRET_UPDATE_ERROR_MESSAGES[data?.jwtSecretUpdateError as JwtSecretUpdateError]
const jwtSecretUpdateProgressMessage =
JWT_SECRET_UPDATE_PROGRESS_MESSAGES[data?.jwtSecretUpdateProgress as JwtSecretUpdateProgress]
const INITIAL_VALUES = {
JWT_EXP: authConfig?.JWT_EXP ?? 3600,
}
const onUpdateJwtExp = async (values: any, { resetForm }: any) => {
if (!projectRef) return console.error('Project ref is required')
updateAuthConfig(
{ projectRef, config: values },
{
onError: (error) => {
toast.error(`Failed to update JWT expiry: ${error?.message}`)
},
onSuccess: () => {
toast.success('Successfully updated JWT expiry')
resetForm({ values, initialValues: values })
},
}
)
}
async function handleJwtSecretUpdate(
jwt_secret: string,
setModalVisibility: Dispatch<SetStateAction<boolean>>
) {
if (!projectRef) return console.error('Project ref is required')
const trackingId = uuidv4()
try {
await updateJwt({ projectRef, jwtSecret: jwt_secret, changeTrackingId: trackingId })
setModalVisibility(false)
toast(
'Successfully submitted JWT secret update request. Please wait while your project is updated.'
)
} catch (error: any) {
toast.error(`Failed to update JWT secret: ${error.message}`)
}
}
return (
<>
<Form
id="jwt-exp-form"
initialValues={INITIAL_VALUES}
onSubmit={onUpdateJwtExp}
validationSchema={schema}
key={authConfig?.JWT_EXP}
>
{({ handleReset, values, initialValues }: any) => {
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
return (
<Panel
footer={
<div className="flex py-4 w-full">
<FormActions
form="jwt-exp-form"
isSubmitting={isUpdatingAuthConfig}
hasChanges={hasChanges}
handleReset={handleReset}
disabled={!canUpdateConfig}
helper={
!canUpdateConfig
? 'You need additional permissions to update JWT settings'
: undefined
}
/>
</div>
}
>
<Panel.Content className="space-y-6 border-t border-panel-border-interior-light [[data-theme*=dark]_&]:border-panel-border-interior-dark">
{isError ? (
<div className="flex items-center justify-center py-8 space-x-2">
<AlertCircle size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">Failed to retrieve JWT settings</p>
</div>
) : (
<>
{legacyKey && legacyKey.status !== 'revoked' && (
<Admonition
type="warning"
title="Legacy JWT secret has been migrated to new JWT Signing Keys"
>
<p className="!leading-normal">
Changing the legacy JWT secret can only be done by rotating to a standby
key and then revoking it. It is used to{' '}
<em className="text-foreground not-italic">
{legacyKey.status === 'in_use' ? 'sign and verify' : 'only verify'}
</em>{' '}
JSON Web Tokens by Supabase products.
</p>
{legacyAPIKeysStatus && legacyAPIKeysStatus.enabled && (
<p className="!leading-normal">
<em className="text-warning not-italic">
This includes the <code>anon</code> and <code>service_role</code> JWT
based API keys.
</em>{' '}
Consider switching to publishable and secret API keys to disable them.
</p>
)}
<Button type="default" asChild icon={<ExternalLink className="size-4" />}>
<Link href={`/project/${projectRef}/settings/api-keys`}>
Go to API keys
</Link>
</Button>
</Admonition>
)}
{legacyKey && legacyKey.status === 'revoked' && (
<Admonition
type="note"
title="Your project has revoked the legacy JWT secret"
description="No new JSON Web Tokens are issued nor verified with it by Supabase products."
/>
)}
<Input
label={
legacyKey?.status === 'revoked'
? 'Revoked legacy JWT secret'
: legacyKey
? 'Legacy JWT secret (still used)'
: 'Legacy JWT secret'
}
readOnly
copy={canReadJWTSecret && isNotUpdatingJwtSecret}
reveal={canReadJWTSecret && isNotUpdatingJwtSecret}
disabled
value={
!canReadJWTSecret
? 'You need additional permissions to view the JWT secret'
: isJwtSecretUpdateFailed
? 'JWT secret update failed'
: isUpdatingJwtSecret
? 'Updating JWT secret...'
: config?.jwt_secret || ''
}
className="input-mono"
descriptionText={
legacyKey?.status === 'revoked'
? 'No longer used to sign JWTs by Supabase Auth.'
: !legacyKey || legacyKey.status === 'in_use'
? 'Used to sign and verify JWTs issued by Supabase Auth.'
: 'Used only to verify JWTs.'
}
layout="horizontal"
/>
<InputNumber
id="JWT_EXP"
name="JWT_EXP"
size="small"
label="Access token expiry time"
descriptionText="How long access tokens are valid for before a refresh token has to be used. Recommendation: 3600 (1 hour)."
layout="horizontal"
actions={<span className="mr-3 text-foreground-lighter">seconds</span>}
disabled={!canUpdateConfig || isLoadingAuthConfig}
/>
{newJwtSecrets && !legacyKey && (
<>
{isUpdatingJwtSecret && (
<div className="flex items-center space-x-2">
<Loader2 className="animate-spin" size={14} />
<p className="text-sm">
Updating JWT secret: {jwtSecretUpdateProgressMessage}
</p>
</div>
)}
{isJwtSecretUpdateFailed && (
<Admonition type="warning" title="Failed to update JWT secret">
Please try again. If the failures persist, please contact Supabase
support with the following details: <br />
Change tracking ID: {data?.changeTrackingId} <br />
Error message: {jwtSecretUpdateErrorMessage}
</Admonition>
)}
<div className="flex flex-col gap-6 border rounded-md bg p-6">
<div className="flex flex-col gap-2">
<h4 className="text-sm">How to change your JWT secret?</h4>
<p className="text-sm text-foreground-light">
Instead of changing the legacy JWT secret use a combination of the JWT
Signing Keys and API keys features. Consider these advantages:
</p>
<ul className="text-sm text-foreground-light list-disc list-inside">
<li>Zero-downtime, reversible change.</li>
<li>Users remain signed in and bad actors out.</li>
<li>
Create multiple secret API keys that are immediately revocable and
fully covered by audit logs.
</li>
<li>
Private keys and shared secrets are no longer visible by
organization members, so they can't leak.
</li>
<li>
Maintain tighter alignment with SOC2 and other security compliance
frameworks.
</li>
<li>
Improve app's performance by using public keys to verify JWTs
instead of calling <code>getUser()</code>.
</li>
</ul>
</div>
<div className="flex flex-row gap-4">
<Button
type="default"
icon={<ExternalLink className="size-4" />}
asChild
>
<Link href={`/project/${projectRef}/settings/api-keys/new`}>
Go to API Keys
</Link>
</Button>
<Button
type="default"
icon={<ExternalLink className="size-4" />}
asChild
>
<Link href={`/project/${projectRef}/settings/jwt/signing-keys`}>
Go to JWT Signing Keys
</Link>
</Button>
<div className="grow" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ButtonTooltip
disabled={!canGenerateNewJWTSecret}
type="default"
iconRight={<ChevronDown size={14} />}
loading={isUpdatingJwtSecret}
tooltip={{
content: {
side: 'bottom',
text: !canGenerateNewJWTSecret
? 'You need additional permissions to generate a new JWT secret'
: undefined,
},
}}
>
Change legacy JWT secret
</ButtonTooltip>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom">
<DropdownMenuItem
className="space-x-2"
onClick={() => setIsGeneratingKey(true)}
>
<RefreshCw size={16} />
<p>Generate a random secret</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="space-x-2"
onClick={() => setIsCreatingKey(true)}
>
<PenTool size={16} />
<p>Create my own secret</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</>
)}
{!newJwtSecrets && (
<div className="space-y-3">
<div className="p-3 px-6 border rounded-md shadow-sm bg-studio">
{isUpdatingJwtSecret ? (
<div className="flex items-center space-x-2">
<Loader2 className="animate-spin" size={14} />
<p className="text-sm">
Updating JWT secret: {jwtSecretUpdateProgressMessage}
</p>
</div>
) : (
<div className="w-full space-y-2">
<div className="flex items-center justify-between w-full">
<div className="flex flex-col space-y-1">
<p className="text-sm">Generate a new JWT secret</p>
<p className="text-sm opacity-50">
A random secret will be created, or you can create your own.
</p>
</div>
<div className="flex flex-col items-end">
{isUpdatingJwtSecret ? (
<Button loading type="secondary">
Updating JWT secret...
</Button>
) : !canGenerateNewJWTSecret ? (
<ButtonTooltip
disabled
type="default"
iconRight={<ChevronDown size={14} />}
tooltip={{
content: {
side: 'bottom',
text: 'You need additional permissions to generate a new JWT secret',
},
}}
>
Generate a new secret
</ButtonTooltip>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="default"
iconRight={<ChevronDown size={14} />}
>
<span>Generate a new secret</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom">
<DropdownMenuItem
className="space-x-2"
onClick={() => setIsGeneratingKey(true)}
>
<RefreshCw size={16} />
<p>Generate a random secret</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="space-x-2"
onClick={() => setIsCreatingKey(true)}
>
<PenTool size={16} />
<p>Create my own secret</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</div>
)}
</div>
{isJwtSecretUpdateFailed ? (
<Admonition type="warning" title="Failed to update JWT secret">
Please try again. If the failures persist, please contact Supabase
support with the following details: <br />
Change tracking ID: {data?.changeTrackingId} <br />
Error message: {jwtSecretUpdateErrorMessage}
</Admonition>
) : null}
</div>
)}
</>
)}
</Panel.Content>
</Panel>
)
}}
</Form>
<TextConfirmModal
variant="destructive"
size="large"
visible={isRegeneratingKey}
title="Confirm legacy JWT secret change"
confirmString="I understand and wish to proceed"
confirmLabel={customToken ? 'Apply custom secret' : 'Generate random secret'}
confirmPlaceholder=""
loading={isSubmittingJwtSecretUpdateRequest}
onCancel={() => {
setIsGeneratingKey(false)
setCustomToken('')
}}
onConfirm={() => handleJwtSecretUpdate(customToken || 'ROLL', setIsGeneratingKey)}
>
<ul className="space-y-4 text-sm">
<li className="flex gap-2 bg border rounded-md p-4">
<Lightbulb size={24} className="flex-shrink-0 text-brand" />
<div className="flex flex-col gap-2">
<p>Use new JWT Signing Keys and API Keys instead</p>
<p className="text-foreground-light">
Consider using a combination of the JWT Signing Keys and API Keys features to
achieve the same effect.{' '}
<em className="text-brand not-italic">
Some or all of the warnings listed below might not apply when using these features
</em>
.
</p>
</div>
</li>
<li className="flex gap-2 px-4">
<CloudOff size={24} className="text-foreground-light flex-shrink-0" />
<div className="flex flex-col gap-2">
<p>Your application will experience significant downtime</p>
<p className="text-foreground-light">
As new <code>anon</code> and <code>service_role</code> keys will be created and the
existing ones permanently destroyed, your application will stop functioning for the
duration it takes you to swap them.{' '}
<em className="text-warning not-italic">
If you have a mobile, desktop, CLI or any offline-capable application the downtime
may be more significant and dependent on app store reviews or user-initiated
upgrades or downloads!
</em>
</p>
<p className="text-foreground-light">
Currently active users will be forcefully signed out (inactive users will keep their
sessions).
</p>
<p className="text-foreground-light">
All long-lived Storage pre-signed URLs will be permanently invalidated.
</p>
</div>
</li>
<li className="flex gap-2 px-4">
<Power size={24} className="text-foreground-light flex-shrink-0" />
<div className="flex flex-col gap-2">
<p>Your project and database will be restarted</p>
<p className="text-foreground-light">
This process restarts your project, terminating existing connections to your
database. You may see API or other unusual errors for{' '}
<em className="text-warning not-italic">up to 2 minutes</em> while the new secret is
deployed.
</p>
</div>
</li>
<li className="flex gap-2 px-4">
<Hourglass size={24} className="text-foreground-light flex-shrink-0" />
<div className="flex flex-col gap-2">
<p>20-minute cooldown period</p>
<p className="text-foreground-light">
Should you need to revert or repeat this operation, it will take at least 20 minutes
before you're able to do so again.
</p>
</div>
</li>
<li className="flex gap-2 px-4">
<TriangleAlert size={24} className="text-foreground-light flex-shrink-0" />
<div className="flex flex-col gap-2">
<p>Irreversible change! This cannot be undone!</p>
<p className="text-foreground-light">
The old JWT secret will be permanently lost (unless you've saved it prior). Even if
you use it again the <code>anon</code> and <code>service_role</code> API keys{' '}
<em className="text-warning not-italic">will not be restorable</em> to their exact
values.
</p>
</div>
</li>
</ul>
</TextConfirmModal>
<Modal
header="Pick a new JWT secret"
visible={isCreatingKey}
size="medium"
variant="danger"
onCancel={() => {
setIsCreatingKey(false)
setCustomToken('')
}}
loading={isSubmittingJwtSecretUpdateRequest}
customFooter={
<div className="space-x-2">
<Button
type="default"
onClick={() => {
setIsCreatingKey(false)
setCustomToken('')
}}
>
Cancel
</Button>
<Button
type="primary"
disabled={
customToken.length < 32 || customToken.includes('@') || customToken.includes('$')
}
loading={isSubmittingJwtSecretUpdateRequest}
onClick={() => {
setIsGeneratingKey(true)
setIsCreatingKey(false)
}}
>
Proceed to final confirmation
</Button>
</div>
}
>
<Modal.Content className="space-y-2">
<p className="text-sm text-foreground-light">
Pick a new custom JWT secret. Make sure it is a strong combination of characters that
cannot be guessed easily.
</p>
<Input
onChange={(e: any) => setCustomToken(e.target.value)}
value={customToken}
icon={<Key />}
type={showCustomTokenInput ? 'text' : 'password'}
className="w-full text-left"
label="Custom JWT secret"
descriptionText="Minimally 32 characters long, '@' and '$' are not allowed."
actions={
<div className="flex items-center justify-center mr-1">
<Button
type="default"
icon={showCustomTokenInput ? <Eye /> : <EyeOff />}
onClick={() => setShowCustomTokenInput(!showCustomTokenInput)}
/>
</div>
}
/>
</Modal.Content>
</Modal>
</>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free