Tillbaka till guider
React Native + WebAuthn
Passkeys i React Native: så fungerar registrering och inloggning
Komplett teknisk guide till hur passkeys fungerar i React Native-appar med SimpleWebAuthn på servern. Från grunderna i FIDO2 och WebAuthn till praktisk implementation av registrering och inloggning.
Introduktion
Passkeys är nästa generation av autentisering – en lösenordsfri metod baserad på kryptografiska nyckelpar som är både säkrare och enklare än traditionella lösenord. Den här guiden förklarar hur passkeys fungerar tekniskt och hur du implementerar dem i en React Native-app med en .NET- eller Node.js-backend.
Guiden bygger på standarderna FIDO2 (ramverket för lösenordsfri autentisering med publika/privata nyckelpar), WebAuthn (API-standarden för hantering av dessa nyckelpar) och Secure Enclave (hårdvaruskyddad lagring som skyddar privata nycklar i iOS och utför kryptografiska operationer).
Vad är en passkey?
En passkey består av två delar som aldrig möts:
På enheten: 🔐 Privat nyckel
- Kan inte exporteras utanför Secure Enclave
- Skyddas av Face ID eller Touch ID
- Lämnar aldrig enheten
På servern: 🔑 Publik nyckel
- Sparas i databasen vid registrering
- Används för att verifiera signaturer
- Kan inte användas för att logga in
Detta innebär att även om servern blir hackad finns det ingen hemlighet att stjäla som kan användas för att logga in. Den privata nyckeln finns bara på användarens enhet och kan inte extraheras.
Passkey-flödet i korthet
Registrering
- Enheten skapar ett nyckelpar (privat + publik nyckel)
- Den publika nyckeln skickas till servern och sparas
- Den privata nyckeln stannar i Secure Enclave på enheten
Inloggning
- Servern skickar en challenge (ett slumpmässigt värde)
- Den privata nyckeln signerar challengen
- Signaturen skickas till servern
- Servern verifierar signaturen med den lagrade publika nyckeln
Säkerhetsfördelar:
- ✅ Privat nyckel lämnar aldrig enheten
- ✅ Servern lagrar endast publik nyckel (ingen hemlighet att stjäla)
- ✅ Ingen lösenordshemlighet behöver delas mellan användare och server
Varför passkeys är det bästa sättet att logga in
Fördelar jämfört med lösenord, SMS-koder och magiska länkar
Phishing-resistent genom domänbindning
Passkeys är kryptografiskt bundna till den domän där de skapades. Om en användare försöker logga in på en falsk webbplats (exempelvis bank-login.se istället för bank.se) kommer iOS eller Android att neka signeringen eftersom domänen inte matchar. Detta skyddar automatiskt mot phishing-attacker, till skillnad från lösenord som kan matas in var som helst eller SMS-koder som kan vidarebefordras till en angripare.
Ingen delad hemlighet på servern
Med traditionella lösenord lagrar servern en hash av lösenordet. Om databasen läcker kan angripare försöka knäcka hasharna. Med passkeys lagrar servern bara den publika nyckeln – som är värdelös för en angripare. Den kan inte användas för att logga in, bara för att verifiera att den som loggar in har den privata nyckeln.
Inget att komma ihåg eller återanvända
Användaren behöver inte komma ihåg ett lösenord, och det finns ingen risk för återanvändning av lösenord mellan olika tjänster. Varje passkey är unik för sin domän och skapas automatiskt av enheten.
Snabbare inloggning
Istället för att skriva in ett lösenord eller vänta på en SMS-kod räcker det med Face ID eller Touch ID. Inloggningen tar sekunder och sker lokalt på enheten utan att vänta på externa leverantörer.
Synkronisering mellan enheter
Moderna plattformar synkroniserar passkeys via iCloud Keychain (Apple) eller Google Password Manager (Android). Det innebär att en passkey skapad på iPhone automatiskt blir tillgänglig på iPad och Mac, och användaren kan logga in på alla sina enheter utan extra registrering.
Realistiska begränsningar och avvägningar
Kontowiederherstlling (account recovery)
Om en användare tappar alla sina enheter och inte har tillgång till sin iCloud Keychain eller Google-konto kan de inte återställa sin passkey. Därför behöver tjänster erbjuda en återställningsmekanism, exempelvis via e-post, telefonnummer eller återställningskoder. Detta är en utmaning som inte finns med centralt lagrade lösenord.
Enhets- och plattformsstöd
Passkeys kräver moderna operativsystem: iOS 16+, Android 9+ och relativt nya webbläsare. Äldre enheter stöds inte, vilket innebär att tjänster måste erbjuda alternativa inloggningsmetoder för äldre plattformar.
Behovet av en fallback-metod
I praktiken måste nästan alla tjänster erbjuda minst en alternativ inloggningsmetod (e-post, telefon, eller lösenord) för användare som inte kan eller vill använda passkeys. Detta innebär att man inte helt kan eliminera den tekniska komplexiteten kring lösenordshantering, även om passkeys är huvudmetoden.
Sammanfattning
Passkeys är det mest säkra och användarvänliga alternativet för de flesta moderna applikationer. De eliminerar de vanligaste säkerhetsproblemen med lösenord och SMS-koder, och ger en smidigare användarupplevelse. Begränsningarna kring återställning och plattformsstöd är hanterbara med rätt design, och fördelarna överväger kraftigt för användare på moderna enheter.
Passkeys vs traditionella metoder
| Aspekt | Lösenord | SMS-koder | Magiska länkar | Passkeys |
|---|---|---|---|---|
| Phishing-skydd | ❌ Kan phishas | ❌ Kan vidarebefordras | ⚠️ Kräver verifierad e-post | ✅ Domänbundet |
| Serverns säkerhet | ❌ Hemlighet på servern | ⚠️ Telefonnummer lagras | ⚠️ Token genereras | ✅ Endast publik nyckel |
| Minnesbart | ❌ Måste komma ihåg | ❌ Temporär kod | ❌ Ingen kod | ✅ Face ID / Touch ID |
| Återanvändningsrisk | ❌ Ofta återanvänt | ✅ Engångskod | ✅ Engångslänk | ✅ Unik nyckel per domän |
| Hastighet | ⚠️ Manuell inmatning | ❌ Vänta på SMS | ❌ Vänta på e-post | ✅ Sekunder med Face ID |
| Synkronisering | ⚠️ Via lösenordshanterare | ❌ Endast en enhet | ❌ Endast e-postkonto | ✅ Via plattform (iCloud, Google) |
Hur fungerar en passkey tekniskt?
Passkeys bygger på asymmetrisk kryptografi. Här är det grundläggande flödet:
sequenceDiagram
participant Server
participant Enhet
Server->>Enhet: Challenge (slumpmässigt värde)
Enhet->>Enhet: Signera med privat nyckel
Enhet-->>Server: Signatur
Server->>Server: Verifiera med publik nyckelFigur 1: Grundläggande passkey-flöde där servern skickar en challenge, enheten signerar den med sin privata nyckel, och servern verifierar signaturen med den lagrade publika nyckeln.
App och domän: Apple App Site Association (AASA)
För att passkeys ska fungera i en iOS-app måste operativsystemet veta att appen äger domänen och att domänen litar på appen. Detta sker via en Apple App Site Association-fil (AASA) som serveras från /.well-known/apple-app-site-association.
Krav som måste uppfyllas:
- ✅ Team ID måste matcha (Apple Developer-teamet som signerar appen)
- ✅ Bundle ID måste matcha (appens unika identifierare)
- ✅ Provisioning Profile måste komma från samma Team ID
- ✅ Associated Domains måste innehålla
webcredentials:din-domän.se - ✅ AASA-filen måste lista
TEAMID.BUNDLEID - ✅ HTTPS krävs för att iOS ska lita på domänen
AASA-flödet
sequenceDiagram
participant iOS
participant Server
iOS->>Server: GET /.well-known/apple-app-site-association
Server-->>iOS: AASA JSON
iOS->>iOS: Verifiera Team ID
iOS->>iOS: Verifiera Bundle ID
iOS->>iOS: Cache AASA
Note over iOS: Passkeys tillåtna för denna app + domänFigur 2: iOS hämtar och verifierar AASA-filen vid första körningen av appen för att fastställa att appen har rätt att använda passkeys för domänen.
Vad är en challenge?
En challenge är ett slumpmässigt engångsvärde som servern genererar och skickar till klienten. Klienten signerar challengen med sin privata nyckel och skickar tillbaka signaturen. Servern verifierar sedan signaturen med den lagrade publika nyckeln.
flowchart LR
A["🎲 Challenge genereras"] --> B["📱 Signeras med privat nyckel"] --> C["🌐 Verifieras av servern"]Figur 3: Challenge-flödet – ett slumpmässigt värde signeras av klienten och verifieras av servern.
Varför behövs en challenge?
- ✅ Bevisar att användaren är närvarande just nu (inte ett återanvänt svar)
- ✅ Stoppar replay-attacker (gammal signatur kan inte återanvändas)
- ✅ Varje inloggning blir unik
- ✅ Kan bara användas en gång
Registreringsflödet i detalj
Översikt
flowchart LR
A["👤 Användarnamn"] --> B["🎲 Challenge"] --> C["👆 Face ID"] --> D["🔐 Nyckelpar skapas"] --> E["✅ Verifiering"] --> F["🎫 Session"]Figur 4: Komplett registreringsflöde från användarnamn till aktiv session.
Vad händer på servern och i iOS?
| 🌐 Servern | 📱 iOS |
|---|---|
| Skapar en challenge | Visar Face ID-prompt |
| Skapar ett användar-ID | Genererar nyckelpar i Secure Enclave |
| Sparar den publika nyckeln | Sparar den privata nyckeln i Secure Enclave |
| Skapar en session | Returnerar credential med publik nyckel |
Sekvensdiagram för registrering
sequenceDiagram
participant App
participant Server
participant SecureEnclave
App->>Server: POST /register/options (användarnamn)
Server-->>App: Challenge + options
App->>SecureEnclave: Begär Face ID
SecureEnclave->>SecureEnclave: Generera nyckelpar
SecureEnclave-->>App: Credential med publik nyckel
App->>Server: POST /register/verify (credential)
Server->>Server: Verifiera challenge, origin, RP ID
Server->>Server: Spara publik nyckel i databasen
Server-->>App: Session tokenFigur 5: Detaljerat sekvensdiagram för registreringsprocessen mellan app, server och Secure Enclave.
Inloggningsflödet i detalj
Översikt
flowchart LR
A["🎲 Challenge"] --> B["👆 Face ID"] --> C["✍️ Signering"] --> D["✅ Verifiering"] --> E["🎫 Session"]Figur 6: Inloggningsflöde – snabbare än registrering eftersom nyckelpar redan finns.
Vad händer på servern och i Secure Enclave?
| 🌐 Servern | 📱 Secure Enclave |
|---|---|
| Skapar en ny challenge | Hittar befintlig credential |
| Verifierar signatur med publik nyckel | Verifierar Face ID |
| Verifierar RP ID (domän) | Signerar challenge med privat nyckel |
| Skapar session | Returnerar assertion med signatur |
Sekvensdiagram för inloggning
sequenceDiagram
participant App
participant Server
participant SecureEnclave
App->>Server: POST /login/options
Server-->>App: Challenge + allowCredentials
App->>SecureEnclave: Begär Face ID
SecureEnclave->>SecureEnclave: Hitta credential
SecureEnclave->>SecureEnclave: Signera challenge
SecureEnclave-->>App: Assertion med signatur
App->>Server: POST /login/verify (assertion)
Server->>Server: Verifiera challenge, origin, RP ID, signatur
Server-->>App: Session tokenFigur 7: Detaljerat sekvensdiagram för inloggningsprocessen.
Secure Enclave: hårdvaruskyddad nyckellagring
flowchart LR
A["📱 App"] --> B["🔐 Secure Enclave"]
B --> C["Privat nyckel"]
C --> D["Signering"]Figur 8: Secure Enclave är en isolerad hårdvarukomponent där privata nycklar lagras och signeringar utförs.
Vad gör Secure Enclave?
- Hårdvaruskyddad nyckellagring – nycklar kan inte extraheras
- Privat nyckel kan inte exporteras, inte ens av appen
- Face ID eller Touch ID krävs innan signering
- Operativsystemet skyddar nyckeln från obehörig åtkomst
Vad signeras egentligen?
När en användare loggar in signeras inte bara challengen – utan en kombination av flera fält:
flowchart TD
A["authenticatorData"]
B["SHA256 av clientDataJSON"]
C["Digital signatur"]
D["Verifieras med publik nyckel"]
A --> C
B --> C
C --> DFigur 9: Signaturen skapas från authenticatorData och en hash av clientDataJSON.
Fält som signeras:
authenticatorData– innehåller RP ID, användarflaggor (UP, UV), counterclientDataJSON– innehåller challenge, origin, typ (create/get)
Detta ger skydd mot både replay-attacker och förfalskning.
Sessionhantering
Efter lyckad inloggning skapar servern en session och returnerar en token till appen.
flowchart LR
A["Inloggning"] --> B["Token"] --> C["SecureStore"] --> D["Automatisk inloggning"]Figur 10: Session-token sparas säkert i SecureStore och används för automatisk inloggning.
| Nyckel | Innehåll |
|---|---|
session_token | Bearer-token för autentisering |
user_id | UUID för användaren |
user_name | Användarens namn |
Sessionens livscykel
flowchart LR
subgraph Start
A["App startar"]
B["Läs SecureStore"]
C{"Token finns?"}
A --> B --> C
end
subgraph Session
D["GET /api/me"]
E["Inloggad"]
F["Logout"]
D --> E --> F
end
subgraph Login
G["Rensa session"]
H["Visa login-skärm"]
G --> H
end
C -->|Ja| D
C -->|Nej| H
D -->|401 Unauthorized| G
F --> GFigur 11: Komplett sessionscykel från appstart till logout.
Säkerhetskontroller
Vid registrering
- ✅ Challenge matchar det som servern genererade
- ✅ Origin verifieras (rätt domän)
- ✅ RP ID verifieras (rätt Relying Party)
- ✅ User Presence (UP) – användaren var närvarande
- ✅ User Verification (UV) – Face ID/Touch ID användes
- ✅ Publik nyckel extraheras och sparas
Vid inloggning
- ✅ Challenge matchar
- ✅ Origin verifieras
- ✅ RP ID verifieras
- ✅ Signatur verifieras med publik nyckel
- ✅ Counter kontrolleras (ökar vid varje användning)
- ✅ Counter uppdateras i databasen
Replay-skydd
flowchart LR
A["Challenge genereras"] --> B["Används en gång"] --> C["Tas bort"]Figur 12: En challenge är en engångsnyckel – används, sedan raderas.
En challenge är en engångsnyckel. Om någon försöker återanvända ett gammalt svar kommer verifieringen att misslyckas eftersom challengen antingen inte finns kvar i serverns cache eller redan har använts.
Skydd mot phishing
flowchart LR
Fake["🎣 Falsk sajt: bank-login.se"]
iOS["📱 iOS / WebAuthn"]
Real["🌐 Äkta sajt: bank.se"]
Fake -->|"rpId = bank-login.se"| iOS
iOS -->|"❌ Signering nekad"| Fake
Real -->|"rpId = bank.se"| iOS
iOS -->|"✅ Signering godkänd"| RealFigur 13: rpId (Relying Party ID) måste matcha domänen där passkeyn skapades.
rpId (Relying Party ID) är den domän som passkeyn skapades för. Om passkeyn skapades för bank.se:
- ✅ Signering tillåts på
bank.se - ❌ Signering nekas på
bank-login.se
En falsk webbplats kan inte använda passkeyn även om användaren blir lurad att besöka den.
Arkitektur: översikt över hela systemet
graph LR
APP["📱 React Native App"]
PASSKEY["🔐 Secure Enclave"]
API["🌐 Backend API"]
WEBAUTHN["🛡️ WebAuthn-logik (SimpleWebAuthn)"]
USERS["👤 Users-tabell"]
CHALLENGES["🎲 Challenges (cache)"]
SESSIONS["🎫 Sessions-tabell"]
APP --> PASSKEY
APP --> API
API --> WEBAUTHN
WEBAUTHN --> USERS
WEBAUTHN --> CHALLENGES
WEBAUTHN --> SESSIONSFigur 14: Fullständig systemarkitektur för en passkey-baserad app.
Komponenter:
- React Native App – användarens app
- Secure Enclave – hårdvaruskyddad nyckellagring i iOS
- Backend API – hanterar endpoints för registrering och inloggning
- WebAuthn-logik – SimpleWebAuthn-bibliotek för verifiering
- Users-tabell – lagrar publika nycklar
- Challenges (cache) – temporär lagring av challenges
- Sessions-tabell – aktiva sessioner
Varför inga lösenord behövs
På enheten:
- Privat nyckel (i Secure Enclave)
- Face ID / Touch ID för verifiering
På servern:
- Publik nyckel (kan inte användas för inloggning)
- Challenge (engångsvärde)
- Session token (efter lyckad inloggning)
Slutsats:
- ❌ Ingen lösenordsdatabas
- ❌ Ingen delad hemlighet mellan användare och server
- ✅ Privat nyckel lämnar aldrig enheten
- ✅ Phishing-resistent autentisering
Kodexempel: registrering
Observera att kodexemplen nedan använder SimpleWebAuthn på servern och ett React Native-bibliotek för passkeys på klienten.
1. Servern skapar challenge och options
const options = await generateRegistrationOptions({
rpID,
rpName,
userID,
userName,
authenticatorSelection: {
residentKey: "required",
userVerification: "required",
authenticatorAttachment: "platform",
},
});
challenges.set(challengeKey, {
userId,
challenge: options.challenge,
});Resultat:
- Challenge genereras och sparas temporärt på servern
- Options skickas till appen
2. Appen skapar passkey
const credential = await createPasskeyFromOptions(options);Under huven:
ASAuthorizationControlleri iOS aktiveras- Secure Enclave genererar nyckelpar
- Privat nyckel lagras i Secure Enclave
- Publik nyckel returneras i
credential
3. Servern verifierar registreringen
const verification = await verifyRegistrationResponse({
response: credential,
expectedChallenge,
expectedOrigin,
expectedRPID,
});Kontrollerar:
- Challenge matchar
- Origin är korrekt
- RP ID är korrekt
- User Verification utfördes
Resultat:
- Publik nyckel extraheras från
credential - Publik nyckel sparas i databasen tillsammans med användar-ID
Kodexempel: inloggning
1. Servern skapar challenge för inloggning
const options = await generateAuthenticationOptions({
rpID,
allowCredentials,
userVerification: "required",
});Resultat:
- Ny challenge skapas och sparas på servern
- Options skickas till appen
2. Enheten signerar challengen
const credential = await authenticateWithOptions(options);Under huven:
- Challenge skickas till Secure Enclave
- Face ID/Touch ID verifieras
- Privat nyckel signerar challengen
- Signatur returneras i
credential
Den privata nyckeln lämnar aldrig enheten.
3. Servern verifierar signaturen
const verification = await verifyAuthenticationResponse({
response: credential,
expectedChallenge,
expectedOrigin,
expectedRPID,
credential: dbCredential,
});Kontrollerar:
- Challenge matchar
- Origin är korrekt
- RP ID är korrekt
- Signatur är giltig (verifieras med publik nyckel)
- Counter har ökat (skydd mot kloning)
Registrering vs inloggning
| Aspekt | Registrering | Inloggning |
|---|---|---|
| Nyckelpar | Skapar nytt nyckelpar | Återanvänder befintligt nyckelpar |
| Publik nyckel | Sparas i databasen | Används för att verifiera signatur |
| WebAuthn-metod | verifyRegistrationResponse | verifyAuthenticationResponse |
| React Native-metod | createPasskey | authenticateWithOptions |
| Counter | Startar på 0 | Ökar vid varje användning |
Vad skyddar varje kontroll mot?
| Kontroll | Skyddar mot |
|---|---|
| Challenge | Replay-attacker (återanvända svar) |
| Origin | Falska webbplatser som inte är din domän |
| RP ID | Phishing (fel domän) |
| User Verification | Obehöriga användare (ingen Face ID) |
| Signatur | Förfalskning av svar |
| Counter | Klonade passkeys |
Sammanfattning
Passkeys är framtidens autentiseringsmetod – säkrare, snabbare och enklare än lösenord. Genom att kombinera asymmetrisk kryptografi med hårdvaruskyddad lagring (Secure Enclave) och biometrisk verifiering (Face ID/Touch ID) skapas en autentiseringsmetod som är:
- Phishing-resistent – domänbunden och kan inte användas på falska sajter
- Säker för servern – ingen hemlighet att stjäla vid databasintrång
- Användarvänlig – snabb inloggning utan att komma ihåg lösenord
- Modern – synkroniseras mellan enheter via iCloud eller Google
Med rätt implementation och en fallback-metod för återställning och äldre enheter är passkeys det bästa valet för moderna applikationer.
Behöver du hjälp med passkeys?
Jag erbjuder komplett implementation av passkeys (WebAuthn) för React Native/Expo-appar med .NET- eller Node.js-backend. Paketet inkluderar både iOS- och Android-stöd, backend-integration, och fallback-flöden.