agentsclimarketplace

Auth

Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/auth

Write better React Native code from day one - opinionated Claude Code rules, New Architecture ready.

Install
npx -y skills add Shankulkarni/react-native-ai-plugin --skill auth

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when implementing auth -- SecureStore for tokens, session management with React Query, Expo Router auth guards, refresh token pattern, sign in/out flow.

SKILL.md

5.0 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

Auth

Tokens in SecureStore. Session in React Query. Guards in Expo Router layouts.

Token Storage (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),
  ]),
}

Session Hook (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 }
}

Expo Router Auth Guards

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 }} />
}

axios Token Interceptor

// 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)
  },
)

Login Screen

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)
}

Sign Out

function ProfileScreen() {
  const { signOut } = useSession()

  return (
    <Pressable onPress={signOut} accessibilityRole="button">
      <Text className="text-red-500">Sign out</Text>
    </Pressable>
  )
}

Rules

  • Tokens in SecureStore only -- never MMKV, never zustand, never AsyncStorage
  • Session state (user object) in React Query -- not zustand
  • Route guards via layout redirects -- never per-screen useEffect auth checks
  • Always show loading state during initial session check -- never flash wrong screen
  • queryClient.clear() on sign out -- removes all cached data
  • Refresh token logic in axios interceptor -- not in components

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.