Autentifikacija u React Native 2026: Clerk, Supabase Auth, expo-auth-session i SecureStore

Praktičan vodič za autentifikaciju u React Native 2026: kombinirajte expo-auth-session, SecureStore, Clerk ili Supabase Auth za siguran login s biometrijom i refresh tokenima.

React Native autentifikacija: Vodič 2026

Ažurirano: 15. lipnja 2026.

Autentifikacija u React Native aplikaciji u 2026. najbrže se implementira kombinacijom expo-auth-session za OAuth tijek, expo-secure-store za sigurnu pohranu tokena na uređaju i jednog upravljanog backenda poput Clerka ili Supabase Autha za izdavanje JWT-ova i upravljanje sesijom. U ovom vodiču pokazat ću kako spojiti te dijelove u produkcijski siguran login tijek, kako dodati biometrijsku potvrdu putem expo-local-authentication i kako izbjeći najčešće zamke s refresh tokenima u Expo SDK 54. Iskreno, ovo je dio React Native projekta gdje sam sam izgubio najviše vremena u zadnje dvije godine, pa ću usput podijeliti i nekoliko pogrešaka koje sam naučio teško.

  • Za većinu novih projekata u 2026. Clerk i Supabase Auth nude najbrži put od nule do produkcije; Firebase Authentication ostaje solidan izbor ako već koristite Firebase ekosustav.
  • expo-auth-session sa useAuthRequest hookom standardni je način pokretanja OAuth/OIDC tijeka unutar Expo aplikacije bez WebViewa.
  • Access tokeni se nikada ne pohranjuju u AsyncStorage ni u Reduxu. Koristite expo-secure-store (Keychain na iOS-u, EncryptedSharedPreferences na Androidu).
  • Biometriju (Face ID, Touch ID, otisak) dodajte kao drugi faktor putem expo-local-authentication, a ne kao zamjenu za stvarnu autentifikaciju.
  • Refresh tokene rotirajte na backendu, držite kratak TTL za access token (5–15 min) i pripremite jasno ponašanje za istek sesije u pozadini.
  • Apple Sign-In je obavezan ako podržavate i Google ili Facebook prijavu na iOS-u; Appleovo recenziranje to izričito zahtijeva.

Što je autentifikacija u React Native aplikaciji?

Autentifikacija je proces dokazivanja identiteta korisnika prema vašem backendu prije nego što mu dopustite pristup zaštićenim resursima. U kontekstu React Native i Expoa, taj se proces sastoji od tri jasno odvojena sloja: prikupljanja kredencijala (email/lozinka, OAuth, OTP, passkey), sigurne razmjene tih kredencijala za par tokena (najčešće JWT access + refresh) i pohrane tokena u zaštićenom dijelu uređaja kako bi se sljedeći API pozivi mogli autorizirati bez ponovnog logiranja.

U 2026. većina timova više ne implementira vlastiti auth server iz nule. Razlog je jednostavan — usklađenost s OWASP MASVS preporukama, rotacijom refresh tokena, GDPR brisanjem računa i WebAuthn passkey podrškom zahtijeva mjesece rada. Umjesto toga, koristi se identity provider (Clerk, Supabase, Auth0, Firebase, AWS Cognito) koji rješava te detalje, a aplikacija govori s njim preko standardnih OAuth 2.1 i OIDC tokova. React Native sloj se onda svodi na: pokreni tijek, primi token, spremi token sigurno, dodaj Authorization header na sve odlazne zahtjeve.

Usporedba: Clerk, Supabase Auth i Firebase Authentication

Izbor identity providera određuje 80% iskustva implementacije. Sva tri rješenja u nastavku imaju službene SDK-ove za Expo i podržavaju EAS Build, ali se razlikuju u modelu cijena, ugrađenim UI komponentama i fleksibilnosti backenda. Tablica ispod sažima razlike koje su me u praksi najviše dijelile.

ZnačajkaClerkSupabase AuthFirebase Auth
Cijena (do 10k MAU)BesplatnoBesplatnoBesplatno
Cijena iznad praga$0.02 po MAUFiksni plan od $25/mj$0.0055 po SMS verifikaciji
Pred-izrađene UI komponenteDa, <SignIn /> komponentaNe, vlastiti UISamo FirebaseUI (zastarjelo)
Passkey podrškaUgrađenoPutem WebAuthnaOgraničeno
OAuth provideri20+15+10+
Vlastiti backendWebhookoviPostgres + RLSCloud Functions
Bez WebViewa za OAuthDa (preko AuthSession)DaDa
Krivulja učenjaNiskaNiska–srednjaSrednja

Moj jednostavan pristup: ako gradite SaaS s organizacijama, B2B ulogama i invitejima, Clerk vas izvuče iz blata u jednom popodnevu. Ako vam treba i baza i auth iz iste ruke, Supabase Auth uz Postgres Row Level Security teško je nadmašiti. Firebase Auth biram samo kad je projekt već duboko u Firebase ekosustavu (Firestore, Cloud Messaging, Crashlytics).

Kako implementirati prijavu pomoću expo-auth-session?

Knjižnica expo-auth-session apstrahira PKCE OAuth tijek tako da ga možete pokrenuti jednim hookom. Ona otvara sustavski ASWebAuthenticationSession na iOS-u i Custom Tabs na Androidu, što znači da korisnik vidi pravi browser s memoriranom sesijom (zato Google login neće tražiti dvofaktorsko ako je već prijavljen u Chromeu), a vaša aplikacija prima authorization code natrag preko deep linka.

Minimalni primjer s Googleom izgleda ovako:

import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
import { useEffect } from 'react';

WebBrowser.maybeCompleteAuthSession();

const discovery = {
  authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
  tokenEndpoint: 'https://oauth2.googleapis.com/token',
  revocationEndpoint: 'https://oauth2.googleapis.com/revoke',
};

export function useGoogleLogin() {
  const redirectUri = AuthSession.makeRedirectUri({
    scheme: 'myapp',
    path: 'oauth-callback',
  });

  const [request, response, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID!,
      scopes: ['openid', 'profile', 'email'],
      redirectUri,
      usePKCE: true,
    },
    discovery,
  );

  useEffect(() => {
    if (response?.type === 'success' && response.params.code) {
      exchangeCodeOnBackend(response.params.code, request!.codeVerifier!);
    }
  }, [response]);

  return { promptAsync, ready: !!request };
}

Ključna stvar koju ljudi promaše: nikada ne razmjenjujte code za token izravno iz aplikacije. Pošaljite code i code_verifier na svoj backend, a backend napravi token exchange i vrati vam svoj vlastiti access token. Tako client secret nikad ne napušta server, a vi dobivate kontrolu nad time koliko dugo tokeni žive.

Sigurna pohrana tokena s expo-secure-store

Klasična greška u React Native projektima — pohrana JWT-a u AsyncStorage. AsyncStorage je obični nekriptirani SQLite/leveldb fajl i svaki rootani uređaj ili jailbreaknuti iPhone može ga pročitati u sekundi. Koristite expo-secure-store koja iza scena koristi iOS Keychain Services i Android EncryptedSharedPreferences (backed by Android Keystore). Tokeni se enkriptiraju ključem koji je vezan za hardver uređaja.

API je minimalan i sinkroni samo na čitanju iz keš sloja:

import * as SecureStore from 'expo-secure-store';

const ACCESS_KEY = 'auth.accessToken';
const REFRESH_KEY = 'auth.refreshToken';

export async function saveTokens(access: string, refresh: string) {
  await SecureStore.setItemAsync(ACCESS_KEY, access, {
    keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
  });
  await SecureStore.setItemAsync(REFRESH_KEY, refresh, {
    keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
    requireAuthentication: true,
  });
}

export async function getAccessToken() {
  return SecureStore.getItemAsync(ACCESS_KEY);
}

export async function clearTokens() {
  await SecureStore.deleteItemAsync(ACCESS_KEY);
  await SecureStore.deleteItemAsync(REFRESH_KEY);
}

Postavljanje requireAuthentication: true na refresh token traži biometriju ili device passcode pri svakom čitanju. Vrlo koristan dodatni sloj za bankarske i healthcare aplikacije. Imajte na umu da ova opcija ne radi na Android emulatoru bez konfiguriranog otiska, pa pripremite testne uvjete. Za state koji nije osjetljiv (npr. user profile cache) i dalje koristite MMKV ili Zustand persist, ali tokene držite isključivo u SecureStoreu.

Clerk u Expo aplikaciji u 10 minuta

Clerk Expo SDK u verziji 2.x koju ćete instalirati u 2026. potpuno se uklapa u Expo Router i radi van kutije s EAS Buildom. Instalacija i konfiguracija svode se na tri koraka.

Prvo, instalirajte pakete:

npx expo install @clerk/clerk-expo expo-secure-store expo-web-browser

Drugo, omotajte root layout s ClerkProvider i podesite tokenCache da koristi SecureStore:

import { ClerkProvider } from '@clerk/clerk-expo';
import * as SecureStore from 'expo-secure-store';
import { Slot } from 'expo-router';

const tokenCache = {
  async getToken(key: string) {
    return SecureStore.getItemAsync(key);
  },
  async saveToken(key: string, value: string) {
    return SecureStore.setItemAsync(key, value);
  },
};

export default function RootLayout() {
  return (
    <ClerkProvider
      publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!}
      tokenCache={tokenCache}
    >
      <Slot />
    </ClerkProvider>
  );
}

Treće, koristite useSignIn, useSignUp i useAuth hookove za sve auth radnje. Clerk automatski osvježava session token (kratak, 1 minuta TTL) na svaki API poziv, što praktički eliminira jezovite scenarije s isteklim sesijama. Ako koristite Expo Router, dodajte zaštitu rute u (authenticated) grupi gdje s useAuth().isSignedIn redirektate na /sign-in. Više detalja o tom obrascu pokrio sam u vodiču o Expo Routeru v6 i zaštićenim rutama.

Za detaljne primjere konfiguracije OAuth providera, pogledajte službeni Clerk Expo quickstart, gdje su opisane točne redirectUrl postavke koje Apple i Google traže.

Supabase Auth s magic linkovima i OAuth providerima

Supabase Auth pristup razlikuje se od Clerkovog po tome što je tijesno vezan uz vašu Postgres bazu. Svaki autenticirani korisnik dobiva auth.users redak na koji možete vezati Row Level Security pravila. To je posebno moćno kad zaštićujete podatke izravno na razini baze, bez middleware sloja.

import { createClient } from '@supabase/supabase-js';
import * as SecureStore from 'expo-secure-store';

const ExpoSecureStoreAdapter = {
  getItem: (key: string) => SecureStore.getItemAsync(key),
  setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
  removeItem: (key: string) => SecureStore.deleteItemAsync(key),
};

export const supabase = createClient(
  process.env.EXPO_PUBLIC_SUPABASE_URL!,
  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
  {
    auth: {
      storage: ExpoSecureStoreAdapter,
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: false,
      flowType: 'pkce',
    },
  },
);

export async function signInWithMagicLink(email: string) {
  const { error } = await supabase.auth.signInWithOtp({
    email,
    options: { emailRedirectTo: 'myapp://auth-callback' },
  });
  if (error) throw error;
}

Magic linkovi rade tako da Supabase pošalje email s linkom koji sadrži deep link u vašu aplikaciju. Kad korisnik klikne, OS otvara aplikaciju, a supabase-js automatski uhvati sesiju iz URL parametara. Da bi to radilo, morate u app.json registrirati scheme i dodati redirect URL u Supabase dashboardu pod Authentication → URL Configuration.

Biometrijska autentifikacija s expo-local-authentication

Korisnici očekuju da nakon prve prijave mogu otvoriti aplikaciju otiskom prsta ili Face ID-jem. expo-local-authentication pruža jednostavan API za to, ali važno je razumjeti što biometrija zapravo radi u kontekstu autentifikacije. Ona ne dokazuje identitet prema vašem backendu, samo dokazuje da je trenutni korisnik uređaja onaj kojeg je OS prepoznao. Drugim riječima, biometrija je lokalna provjera koja otključava lokalno spremljeni refresh token; backend nikad ne zna je li korisnik koristio Face ID ili lozinku.

import * as LocalAuthentication from 'expo-local-authentication';
import { getRefreshToken } from './secureStore';

export async function unlockWithBiometrics() {
  const hasHardware = await LocalAuthentication.hasHardwareAsync();
  const enrolled = await LocalAuthentication.isEnrolledAsync();
  if (!hasHardware || !enrolled) {
    throw new Error('Biometrija nije dostupna na ovom uređaju');
  }

  const result = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Otključajte aplikaciju',
    fallbackLabel: 'Koristi PIN',
    disableDeviceFallback: false,
  });

  if (!result.success) {
    return null;
  }

  return getRefreshToken();
}

Tipičan obrazac: pri prvom loginu spremite refresh token u SecureStore s requireAuthentication: true. Pri svakom otvaranju aplikacije pozovite unlockWithBiometrics(). Ako uspije, povucite refresh token i razmijenite ga za svjež access token. Ako biometrija ne uspije ili nije postavljena, vratite korisnika na ekran s lozinkom. Ovaj tijek povezuje se i s push notifikacijama; cijeli scenarij pokrio sam u vodiču o Expo Notifications i FCM HTTP v1 integraciji, gdje token za pretplatu ovisi o autentikaciji.

Refresh tokeni i upravljanje sesijom

Refresh tokeni su mjesto gdje većina implementacija pada. Pravilo je jednostavno: access token kratak (5–15 minuta), refresh token dug (7–30 dana), rotirajte refresh pri svakoj upotrebi i poništite ga ako se isti token pojavi dvaput (replay detection). Koristite biblioteku poput axios-auth-refresh ili napišite tanki interceptor koji presreće 401 odgovore:

import axios from 'axios';
import { getAccessToken, getRefreshToken, saveTokens } from './secureStore';

const api = axios.create({ baseURL: process.env.EXPO_PUBLIC_API_URL });

let refreshing: Promise<string> | null = null;

api.interceptors.request.use(async (config) => {
  const token = await getAccessToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

api.interceptors.response.use(
  (r) => r,
  async (error) => {
    if (error.response?.status !== 401 || error.config._retry) {
      return Promise.reject(error);
    }
    error.config._retry = true;
    refreshing ??= refreshTokens();
    const newAccess = await refreshing.finally(() => (refreshing = null));
    error.config.headers.Authorization = `Bearer ${newAccess}`;
    return api(error.config);
  },
);

async function refreshTokens(): Promise<string> {
  const refresh = await getRefreshToken();
  const { data } = await axios.post(
    `${process.env.EXPO_PUBLIC_API_URL}/auth/refresh`,
    { refresh },
  );
  await saveTokens(data.access, data.refresh);
  return data.access;
}

Trik s refreshing promiseom rješava race condition kad više paralelnih zahtjeva istovremeno dobije 401. Bez njega bi se refresh poslao više puta i obično bi backend invalidirao token jer detektira reuse. Sjećam se da sam upravo taj bug lovio cijeli vikend prije jednog launcha; otkad imam ovaj pattern, nije se vratio. Za referencu, OAuth 2.1 draft preporučuje upravo ovaj pristup u OAuth 2.1 specifikaciji.

Najčešće zamke kod implementacije

U projektima koje sam vodio u zadnjih dvije godine, ovih pet problema ponavlja se nepogrešivo. Provjerite ih prije nego što pošaljete build u TestFlight.

  1. App Tracking Transparency i OAuth. Apple od iOS-a 17 zahtijeva ATT prompt prije bilo kakvog cross-app trackinga; ako koristite Facebook ili Google login s analytics SDK-om, redoslijed mora biti: prikaži ATT, čekaj odgovor, tek onda pokreni OAuth.
  2. Apple Sign-In je obavezan. Ako u svojoj aplikaciji nudite Google, Facebook ili bilo koji social login, App Store guideline 4.8 vas obvezuje da uz to ponudite i Sign in with Apple. Preskočiti ga znači odbijanje review-a.
  3. Deep link greške nakon ažuriranja. Kad mijenjate scheme u app.json, MORATE ponovno generirati ios i android nativne datoteke s npx expo prebuild --clean, inače OAuth callback nikad ne dođe natrag. Ovo je posebno opasno na EAS Updateu, jer JS bundle se ažurira, ali scheme ostaje stari.
  4. Logout koji ne briše sve. Pri odjavi obrišite SecureStore, MMKV cache, TanStack Query cache i resetirajte Zustand store. Inače sljedeći korisnik na istom uređaju vidi tuđe podatke u kratkom trenutku prije nego što novi fetch završi.
  5. Zaboravljeni WebBrowser.maybeCompleteAuthSession(). Bez tog poziva na vrhu modula, AuthSession popup ostaje otvoren nakon callbacka na Android release buildovima, dok na dev modu radi normalno. Klasičan razlog za panični patch petak prije launcha.

Sigurnosni sloj ne završava na implementaciji. Preporučujem da prije produkcije provedete osnovni audit prema OWASP MASVS-u. Pokriva pohranu, kriptografiju i interakciju s platformom, i daje vam check listu koju review timovi (i bilo koji budući security auditor) prepoznaju.

Često postavljana pitanja

Mogu li koristiti AsyncStorage za pohranu JWT tokena?

Ne. AsyncStorage sprema podatke u običan, neenkriptirani fajl koji je čitljiv na rootanim Android uređajima i jailbreaknutim iPhone-ima. Koristite expo-secure-store koji ide preko iOS Keychaina i Android Keystorea s hardverski vezanim ključem.

Što je bolje za React Native: Clerk ili Supabase Auth?

Clerk je brži za B2B aplikacije s organizacijama, ulogama i pozivnicama jer ima ugrađene UI komponente i webhookove. Supabase Auth je bolji izbor kad već koristite Postgres, jer dobivate Row Level Security iz iste kontrole pristupa. Za jednostavne consumer aplikacije, oba rade odlično.

Kako dodati Sign in with Apple u Expo aplikaciju?

Instalirajte expo-apple-authentication, omotajte gumb s AppleAuthentication.AppleAuthenticationButton i pozovite signInAsync s FULL_NAME i EMAIL scopes. Identity token koji vratite šaljete na backend, gdje ga verificirate Appleovim javnim ključem prije izdavanja vlastite sesije.

Treba li mi backend ako koristim Clerk ili Supabase?

Ne za sam auth. I Clerk i Supabase vam izdaju JWT koji možete verificirati. Backend trebate samo ako imate poslovnu logiku ili podatke koji nisu u Supabase Postgresu. Za čiste mobile-first projekte Clerk + edge funkcije ili Supabase + RLS često su dovoljni.

Kako testirati autentifikaciju u Detoxu ili Maestru?

Najbolja praksa je dodati "test mode" flag u dev builds koji preskače OAuth tijek i koristi signInWithPassword sa seed računima. Detox device.launchApp({launchArgs: {testMode: 'true'}}) i Maestrov env blok prosljeđuju varijable koje JS kod prepoznaje i koristi za hardkodirane testne kredencijale.

O Autoru Editorial Team

Our team of expert writers and editors.