eidguide.dev

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

På servern: 🔑 Publik nyckel

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

  1. Enheten skapar ett nyckelpar (privat + publik nyckel)
  2. Den publika nyckeln skickas till servern och sparas
  3. Den privata nyckeln stannar i Secure Enclave på enheten

Inloggning

  1. Servern skickar en challenge (ett slumpmässigt värde)
  2. Den privata nyckeln signerar challengen
  3. Signaturen skickas till servern
  4. Servern verifierar signaturen med den lagrade publika nyckeln

Säkerhetsfördelar:

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

AspektLösenordSMS-koderMagiska länkarPasskeys
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 nyckel

Figur 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:

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än

Figur 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?

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 challengeVisar Face ID-prompt
Skapar ett användar-IDGenererar nyckelpar i Secure Enclave
Sparar den publika nyckelnSparar den privata nyckeln i Secure Enclave
Skapar en sessionReturnerar 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 token

Figur 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 challengeHittar befintlig credential
Verifierar signatur med publik nyckelVerifierar Face ID
Verifierar RP ID (domän)Signerar challenge med privat nyckel
Skapar sessionReturnerar 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 token

Figur 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?

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 --> D

Figur 9: Signaturen skapas från authenticatorData och en hash av clientDataJSON.

Fält som signeras:

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.

NyckelInnehåll
session_tokenBearer-token för autentisering
user_idUUID för användaren
user_nameAnvä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 --> G

Figur 11: Komplett sessionscykel från appstart till logout.

Säkerhetskontroller

Vid registrering

Vid inloggning

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"| Real

Figur 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:

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 --> SESSIONS

Figur 14: Fullständig systemarkitektur för en passkey-baserad app.

Komponenter:

Varför inga lösenord behövs

På enheten:

På servern:

Slutsats:

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:

2. Appen skapar passkey

const credential = await createPasskeyFromOptions(options);

Under huven:

3. Servern verifierar registreringen

const verification = await verifyRegistrationResponse({
  response: credential,
  expectedChallenge,
  expectedOrigin,
  expectedRPID,
});

Kontrollerar:

Resultat:

Kodexempel: inloggning

1. Servern skapar challenge för inloggning

const options = await generateAuthenticationOptions({
  rpID,
  allowCredentials,
  userVerification: "required",
});

Resultat:

2. Enheten signerar challengen

const credential = await authenticateWithOptions(options);

Under huven:

Den privata nyckeln lämnar aldrig enheten.

3. Servern verifierar signaturen

const verification = await verifyAuthenticationResponse({
  response: credential,
  expectedChallenge,
  expectedOrigin,
  expectedRPID,
  credential: dbCredential,
});

Kontrollerar:

Registrering vs inloggning

AspektRegistreringInloggning
NyckelparSkapar nytt nyckelparÅteranvänder befintligt nyckelpar
Publik nyckelSparas i databasenAnvänds för att verifiera signatur
WebAuthn-metodverifyRegistrationResponseverifyAuthenticationResponse
React Native-metodcreatePasskeyauthenticateWithOptions
CounterStartar på 0Ökar vid varje användning

Vad skyddar varje kontroll mot?

KontrollSkyddar mot
ChallengeReplay-attacker (återanvända svar)
OriginFalska webbplatser som inte är din domän
RP IDPhishing (fel domän)
User VerificationObehöriga användare (ingen Face ID)
SignaturFörfalskning av svar
CounterKlonade 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:

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.