mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
perf: use getClaims for API endpoint auth (#39311)
Use getClaims instead of getUser which avoids a network call to GoTrue to validate the user in case of asymmetric keys - this shaves off a good amount of latency for every API call.
This commit is contained in:
1 parent
1f245734ae
commit
f0436716f9
5 files changed
+30
-42
No files matched your search
@@ -3,9 +3,9 @@ import { apiAuthenticate } from './apiAuthenticate'
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
getAuthUser: vi.fn().mockResolvedValue({
|
||||
user: {
|
||||
id: 'test-gotrue-id',
|
||||
getUserClaims: vi.fn().mockResolvedValue({
|
||||
claims: {
|
||||
sub: 'test-gotrue-id',
|
||||
email: 'test@example.com',
|
||||
},
|
||||
error: null,
|
||||
@@ -14,7 +14,7 @@ const mocks = vi.hoisted(() => {
|
||||
})
|
||||
|
||||
vi.mock('lib/gotrue', () => ({
|
||||
getAuthUser: mocks.getAuthUser,
|
||||
getUserClaims: mocks.getUserClaims,
|
||||
}))
|
||||
|
||||
describe('apiAuthenticate', () => {
|
||||
@@ -29,9 +29,9 @@ describe('apiAuthenticate', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.getAuthUser.mockResolvedValue({
|
||||
user: {
|
||||
id: 'test-gotrue-id',
|
||||
mocks.getUserClaims.mockResolvedValue({
|
||||
claims: {
|
||||
sub: 'test-gotrue-id',
|
||||
email: 'test@example.com',
|
||||
},
|
||||
error: null,
|
||||
@@ -45,8 +45,8 @@ describe('apiAuthenticate', () => {
|
||||
})
|
||||
|
||||
it('should return error when auth user fetch fails', async () => {
|
||||
mocks.getAuthUser.mockResolvedValue({
|
||||
user: null,
|
||||
mocks.getUserClaims.mockResolvedValue({
|
||||
claims: null,
|
||||
error: new Error('Auth failed'),
|
||||
})
|
||||
|
||||
@@ -55,8 +55,8 @@ describe('apiAuthenticate', () => {
|
||||
})
|
||||
|
||||
it('should return error when user does not exist', async () => {
|
||||
mocks.getAuthUser.mockResolvedValue({
|
||||
user: null,
|
||||
mocks.getUserClaims.mockResolvedValue({
|
||||
claims: null,
|
||||
error: null,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getAuthUser } from 'lib/gotrue'
|
||||
import type { JwtPayload } from '@supabase/supabase-js'
|
||||
import { getUserClaims } from 'lib/gotrue'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { ResponseError, SupaResponse, User } from 'types'
|
||||
import type { ResponseError } from 'types'
|
||||
|
||||
/**
|
||||
* Use this method on api routes to check if user is authenticated and having required permissions.
|
||||
@@ -15,14 +16,14 @@ import type { ResponseError, SupaResponse, User } from 'types'
|
||||
export async function apiAuthenticate(
|
||||
req: NextApiRequest,
|
||||
_res: NextApiResponse
|
||||
): Promise<SupaResponse<User>> {
|
||||
): Promise<JwtPayload | { error: ResponseError }> {
|
||||
try {
|
||||
const user = await fetchUser(req)
|
||||
if (!user) {
|
||||
const claims = await fetchUserClaims(req)
|
||||
if (!claims) {
|
||||
return { error: new Error('The user does not exist') }
|
||||
}
|
||||
|
||||
return user
|
||||
return claims
|
||||
} catch (error) {
|
||||
return { error: error as ResponseError }
|
||||
}
|
||||
@@ -32,19 +33,19 @@ export async function apiAuthenticate(
|
||||
* @returns
|
||||
* user with only id prop or detail object. It depends on requireUserDetail config
|
||||
*/
|
||||
async function fetchUser(req: NextApiRequest): Promise<any> {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '')
|
||||
async function fetchUserClaims(req: NextApiRequest): Promise<JwtPayload> {
|
||||
const token = req.headers.authorization?.replace(/bearer /i, '')
|
||||
if (!token) {
|
||||
throw new Error('missing access token')
|
||||
}
|
||||
const { user, error } = await getAuthUser(token)
|
||||
const { claims, error } = await getUserClaims(token)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (!claims) {
|
||||
throw new Error('The user does not exist')
|
||||
}
|
||||
|
||||
return user
|
||||
return claims
|
||||
}
|
||||
@@ -40,9 +40,6 @@ export default async function apiWrapper(
|
||||
message: `Unauthorized: ${response.error.message}`,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// Attach user information to request parameters
|
||||
;(req as any).user = response
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,4 @@ describe('apiWrapper', () => {
|
||||
expect(mockHandler).toHaveBeenCalledWith(mockReq, mockRes)
|
||||
expect(apiAuthenticate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should attach user to request and call handler when authentication succeeds', async () => {
|
||||
const mockUser = { id: '123', email: 'test@example.com' } as any as any
|
||||
vi.mocked(apiAuthenticate).mockResolvedValue(mockUser)
|
||||
|
||||
await apiWrapper(mockReq, mockRes, mockHandler, { withAuth: true })
|
||||
|
||||
expect(mockReq.user).toEqual(mockUser)
|
||||
expect(mockHandler).toHaveBeenCalledWith(mockReq, mockRes)
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JwtPayload } from '@supabase/supabase-js'
|
||||
import { getAccessToken, type User } from 'common/auth'
|
||||
import { gotrueClient } from 'common/gotrue'
|
||||
|
||||
@@ -23,18 +24,17 @@ export const validateReturnTo = (
|
||||
return safePathPattern.test(returnTo) ? returnTo : fallback
|
||||
}
|
||||
|
||||
export const getAuthUser = async (token: String): Promise<any> => {
|
||||
export const getUserClaims = async (
|
||||
token: String
|
||||
): Promise<{ error: any | null; claims: JwtPayload | null }> => {
|
||||
try {
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await auth.getUser(token.replace('Bearer ', ''))
|
||||
const { data, error } = await auth.getClaims(token.replace(/bearer /i, ''))
|
||||
if (error) throw error
|
||||
|
||||
return { user, error: null }
|
||||
return { claims: data?.claims ?? null, error: null }
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return { user: null, error: err }
|
||||
return { claims: null, error: err }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user