mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat: jwt signing keys front-and-center (#40752)
This commit is contained in:
1 parent
34ca04300c
commit
51305ab6c0
6 files changed
+107
-108
No files matched your search
@@ -36,7 +36,7 @@ We've designed the Signing keys system to address many problems the legacy syste
|
||||
|
||||
You can start migrating away from the legacy JWT secret through the Supabase dashboard. This process does not cause downtime for your application.
|
||||
|
||||
1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt/signing-keys) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system.
|
||||
1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system.
|
||||
2. Simultaneously, we're creating a new asymmetric JWT signing key for you to rotate to. This key starts off as standby key -- meaning it's being advertised as a key that Supabase Auth will use in the future to create JWTs.
|
||||
3. If you're not ready to switch away from the legacy JWT secret right now, you can stop here without any issue. If you wish to use a different signing key -- either to use a different signing algorithm (RSA, Elliptic Curve or shared secret) or to import a private key or shared secret you already have -- feel free to move the standby key to _Previously used_ before finally moving it to _Revoked._
|
||||
4. If you do wish to start using the standby key for all new JWT use the _Rotate keys_ button. A few important notes:
|
||||
@@ -176,7 +176,7 @@ supabase gen signing-key --algorithm ES256
|
||||
|
||||
Make sure you store this private key in a secure location, as it will not be extractable from Supabase.
|
||||
|
||||
To import the generated private key to your project, create a [new standby key](/dashboard/project/_/settings/jwt/signing-keys) from the dashboard:
|
||||
To import the generated private key to your project, create a [new standby key](/dashboard/project/_/settings/jwt) from the dashboard:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -3,27 +3,21 @@ import { ScaffoldContainer } from 'components/layouts/Scaffold'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
|
||||
const JWTKeysLayout = ({ children }: PropsWithChildren) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys'])
|
||||
|
||||
const navigationItems = [
|
||||
...(projectSettingsLegacyJwtKeys
|
||||
? [
|
||||
{
|
||||
label: 'Legacy JWT Secret',
|
||||
href: `/project/${projectRef}/settings/jwt`,
|
||||
id: 'legacy-jwt-keys',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'JWT Signing Keys',
|
||||
href: `/project/${projectRef}/settings/jwt/signing-keys`,
|
||||
href: `/project/${projectRef}/settings/jwt`,
|
||||
id: 'signing-keys',
|
||||
},
|
||||
{
|
||||
label: 'Legacy JWT Secret',
|
||||
href: `/project/${projectRef}/settings/jwt/legacy`,
|
||||
id: 'legacy-jwt-keys',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
|
||||
@@ -242,6 +242,11 @@ const nextConfig = {
|
||||
destination: '/org/_/billing',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/project/:ref/settings/jwt/signing-keys',
|
||||
destination: '/project/:ref/settings/jwt',
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/database/api-logs',
|
||||
destination: '/project/:ref/logs/edge-logs',
|
||||
|
||||
@@ -1,70 +1,39 @@
|
||||
import JWTSettings from 'components/interfaces/JwtSecrets/jwt-settings'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { JWTSecretKeysTable } from 'components/interfaces/JwtSecrets/jwt-secret-keys-table'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout'
|
||||
import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from 'components/interfaces/JwtSecrets/jwt.constants'
|
||||
import { UnknownInterface } from 'components/ui/UnknownInterface'
|
||||
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
|
||||
import { configKeys } from 'data/config/keys'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const JWTKeysLegacyPage: NextPageWithLayout = () => {
|
||||
const client = useQueryClient()
|
||||
const { ref: projectRef } = useParams()
|
||||
const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys'])
|
||||
|
||||
const { data } = useJwtSecretUpdatingStatusQuery(
|
||||
{ projectRef },
|
||||
{ enabled: projectSettingsLegacyJwtKeys }
|
||||
const JWTSigningKeysPage: NextPageWithLayout = () => {
|
||||
const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
|
||||
PermissionAction.READ,
|
||||
'auth_signing_keys'
|
||||
)
|
||||
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
|
||||
const jwtSecretUpdateError = data?.jwtSecretUpdateError
|
||||
|
||||
const previousJwtSecretUpdateStatus = useRef<JwtSecretUpdateStatus>()
|
||||
const { Failed, Updated, Updating } = JwtSecretUpdateStatus
|
||||
const jwtSecretUpdateErrorMessage =
|
||||
JWT_SECRET_UPDATE_ERROR_MESSAGES[jwtSecretUpdateError as JwtSecretUpdateError]
|
||||
|
||||
useEffect(() => {
|
||||
if (previousJwtSecretUpdateStatus.current === Updating) {
|
||||
switch (jwtSecretUpdateStatus) {
|
||||
case Updated:
|
||||
client.invalidateQueries({ queryKey: configKeys.api(projectRef) })
|
||||
client.invalidateQueries({ queryKey: configKeys.settings(projectRef) })
|
||||
client.invalidateQueries({ queryKey: configKeys.postgrest(projectRef) })
|
||||
toast.success('Successfully updated JWT secret')
|
||||
break
|
||||
case Failed:
|
||||
toast.error(`JWT secret update failed: ${jwtSecretUpdateErrorMessage}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
previousJwtSecretUpdateStatus.current = jwtSecretUpdateStatus
|
||||
}, [jwtSecretUpdateStatus])
|
||||
|
||||
if (!projectSettingsLegacyJwtKeys) {
|
||||
return <UnknownInterface urlBack={`/project/${projectRef}/settings/jwt/signing-keys`} />
|
||||
}
|
||||
|
||||
return (
|
||||
<JWTKeysLayout>
|
||||
<JWTSettings />
|
||||
</JWTKeysLayout>
|
||||
<>
|
||||
{!isPermissionsLoaded ? (
|
||||
<GenericSkeletonLoader />
|
||||
) : !canReadAPIKeys ? (
|
||||
<NoPermission isFullPage resourceText="access your project's API keys" />
|
||||
) : (
|
||||
<JWTSecretKeysTable />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
JWTKeysLegacyPage.getLayout = (page) => (
|
||||
JWTSigningKeysPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<SettingsLayout>{page}</SettingsLayout>
|
||||
<SettingsLayout>
|
||||
<JWTKeysLayout>{page}</JWTKeysLayout>
|
||||
</SettingsLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default JWTKeysLegacyPage
|
||||
export default JWTSigningKeysPage
|
||||
@@ -0,0 +1,70 @@
|
||||
import JWTSettings from 'components/interfaces/JwtSecrets/jwt-settings'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout'
|
||||
import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from 'components/interfaces/JwtSecrets/jwt.constants'
|
||||
import { UnknownInterface } from 'components/ui/UnknownInterface'
|
||||
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
|
||||
import { configKeys } from 'data/config/keys'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const JWTKeysLegacyPage: NextPageWithLayout = () => {
|
||||
const client = useQueryClient()
|
||||
const { ref: projectRef } = useParams()
|
||||
const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys'])
|
||||
|
||||
const { data } = useJwtSecretUpdatingStatusQuery(
|
||||
{ projectRef },
|
||||
{ enabled: projectSettingsLegacyJwtKeys }
|
||||
)
|
||||
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
|
||||
const jwtSecretUpdateError = data?.jwtSecretUpdateError
|
||||
|
||||
const previousJwtSecretUpdateStatus = useRef<JwtSecretUpdateStatus>()
|
||||
const { Failed, Updated, Updating } = JwtSecretUpdateStatus
|
||||
const jwtSecretUpdateErrorMessage =
|
||||
JWT_SECRET_UPDATE_ERROR_MESSAGES[jwtSecretUpdateError as JwtSecretUpdateError]
|
||||
|
||||
useEffect(() => {
|
||||
if (previousJwtSecretUpdateStatus.current === Updating) {
|
||||
switch (jwtSecretUpdateStatus) {
|
||||
case Updated:
|
||||
client.invalidateQueries({ queryKey: configKeys.api(projectRef) })
|
||||
client.invalidateQueries({ queryKey: configKeys.settings(projectRef) })
|
||||
client.invalidateQueries({ queryKey: configKeys.postgrest(projectRef) })
|
||||
toast.success('Successfully updated JWT secret')
|
||||
break
|
||||
case Failed:
|
||||
toast.error(`JWT secret update failed: ${jwtSecretUpdateErrorMessage}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
previousJwtSecretUpdateStatus.current = jwtSecretUpdateStatus
|
||||
}, [jwtSecretUpdateStatus])
|
||||
|
||||
if (!projectSettingsLegacyJwtKeys) {
|
||||
return <UnknownInterface urlBack={`/project/${projectRef}/settings/jwt/signing-keys`} />
|
||||
}
|
||||
|
||||
return (
|
||||
<JWTKeysLayout>
|
||||
<JWTSettings />
|
||||
</JWTKeysLayout>
|
||||
)
|
||||
}
|
||||
|
||||
JWTKeysLegacyPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<SettingsLayout>{page}</SettingsLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default JWTKeysLegacyPage
|
||||
@@ -1,39 +0,0 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { JWTSecretKeysTable } from 'components/interfaces/JwtSecrets/jwt-secret-keys-table'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout'
|
||||
import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const JWTSigningKeysPage: NextPageWithLayout = () => {
|
||||
const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
|
||||
PermissionAction.READ,
|
||||
'auth_signing_keys'
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isPermissionsLoaded ? (
|
||||
<GenericSkeletonLoader />
|
||||
) : !canReadAPIKeys ? (
|
||||
<NoPermission isFullPage resourceText="access your project's API keys" />
|
||||
) : (
|
||||
<JWTSecretKeysTable />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
JWTSigningKeysPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<SettingsLayout>
|
||||
<JWTKeysLayout>{page}</JWTKeysLayout>
|
||||
</SettingsLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default JWTSigningKeysPage
|
||||
Reference in new issue
Block a user