auth — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited auth (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Tokens in SecureStore. Session in React Query. Guards in Expo Router layouts.
src/libs/token.ts)import * as SecureStore from 'expo-secure-store'
const ACCESS_KEY = 'access_token'
const REFRESH_KEY = 'refresh_token'
export const tokenStorage = {
getAccess: () => SecureStore.getItemAsync(ACCESS_KEY),
setAccess: (token: string) => SecureStore.setItemAsync(ACCESS_KEY, token),
getRefresh: () => SecureStore.getItemAsync(REFRESH_KEY),
setRefresh: (token: string) => SecureStore.setItemAsync(REFRESH_KEY, token),
clear: () => Promise.all([
SecureStore.deleteItemAsync(ACCESS_KEY),
SecureStore.deleteItemAsync(REFRESH_KEY),
]),
}src/hooks/use-session.ts)import { useQuery, useQueryClient } from '@tanstack/react-query'
import { tokenStorage } from '@/libs/token'
import { api } from '@/libs/axios'
const SESSION_KEY = ['session']
export function useSession() {
const queryClient = useQueryClient()
const { data: session, isPending } = useQuery({
queryKey: SESSION_KEY,
queryFn: async () => {
const token = await tokenStorage.getAccess()
if (!token) return null
const { data } = await api.get('/auth/me')
return data
},
staleTime: 5 * 60 * 1000, // revalidate session every 5 min
})
const signIn = async (tokens: { access: string; refresh: string }) => {
await Promise.all([
tokenStorage.setAccess(tokens.access),
tokenStorage.setRefresh(tokens.refresh),
])
await queryClient.invalidateQueries({ queryKey: SESSION_KEY })
}
const signOut = async () => {
await tokenStorage.clear()
queryClient.setQueryData(SESSION_KEY, null)
queryClient.clear()
}
return { session, isPending, signIn, signOut }
}app/(auth)/_layout.tsx -- guest only, redirects logged-in users:
import { Redirect, Stack } from 'expo-router'
import { useSession } from '@/hooks/use-session'
import { ActivityIndicator, View } from 'react-native'
export default function AuthLayout() {
const { session, isPending } = useSession()
if (isPending) {
return (
<View className="flex-1 items-center justify-center">
<ActivityIndicator />
</View>
)
}
if (session) return <Redirect href="/(app)/(tabs)/" />
return <Stack screenOptions={{ headerShown: false }} />
}app/(app)/_layout.tsx -- protected, redirects guests:
import { Redirect, Stack } from 'expo-router'
import { useSession } from '@/hooks/use-session'
export default function AppLayout() {
const { session, isPending } = useSession()
if (isPending) return null // splash screen shows during check
if (!session) return <Redirect href="/(auth)/login" />
return <Stack screenOptions={{ headerShown: false }} />
}// src/libs/axios.ts
api.interceptors.request.use(async (config) => {
const token = await tokenStorage.getAccess()
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// Refresh token on 401
api.interceptors.response.use(
(res) => res,
async (error) => {
const original = error.config
if (error.response?.status === 401 && !original._retry) {
original._retry = true
try {
const refresh = await tokenStorage.getRefresh()
const { data } = await axios.post(`${process.env.EXPO_PUBLIC_API_URL}/auth/refresh`, { refresh })
await tokenStorage.setAccess(data.access)
original.headers.Authorization = `Bearer ${data.access}`
return api(original)
} catch {
await tokenStorage.clear()
// Redirect to login -- handled by router guard
}
}
return Promise.reject(error)
},
)function LoginScreen() {
const { signIn } = useSession()
const { mutate: login, isPending } = useMutation({
mutationFn: (values: LoginFormValues) => api.post('/auth/login', values).then(r => r.data),
onSuccess: async (data) => {
await signIn({ access: data.accessToken, refresh: data.refreshToken })
// Router guard handles redirect automatically
},
})
// ...form using react-hook-form (see forms skill)
}function ProfileScreen() {
const { signOut } = useSession()
return (
<Pressable onPress={signOut} accessibilityRole="button">
<Text className="text-red-500">Sign out</Text>
</Pressable>
)
}SecureStore only -- never MMKV, never zustand, never AsyncStorageuseEffect auth checksqueryClient.clear() on sign out -- removes all cached data~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.