diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,13 @@
+### 0.10.0.0
+
+* [#184](https://github.com/tweag/webauthn/pull/184) Pass a list of allowed origins instead of a single origin.
+  This is a breaking change needed for allowing native apps to use WebAuthn. It is also needed for Relying Parties
+  that want to allow multiple subdomains to access WebAuthn credentials.
+  Unlike the rest of this library, which strictly follows the L2 version of this spec, this feature is defined
+  in the [L3 draft](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). However because WebAuthn on
+  Native Apps is widely deployed through the push of Passkeys we decided to include this feature in this library early.
+
+
 ### 0.9.0.0
 
 * [#182](https://github.com/tweag/webauthn/pull/182) Migrate to the crypton library ecosystem.
diff --git a/src/Crypto/WebAuthn/Operation/Authentication.hs b/src/Crypto/WebAuthn/Operation/Authentication.hs
--- a/src/Crypto/WebAuthn/Operation/Authentication.hs
+++ b/src/Crypto/WebAuthn/Operation/Authentication.hs
@@ -36,6 +36,7 @@
 import Data.List.NonEmpty (NonEmpty)
 import Data.Text (Text)
 import Data.Validation (Validation)
+import qualified Data.List.NonEmpty as NE
 
 -- | Errors that may occur during [assertion](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion)
 data AuthenticationError
@@ -74,11 +75,11 @@
         -- | The challenge received from the client, part of the response
         aeReceivedChallenge :: M.Challenge
       }
-  | -- | The origin derived by the client does match the assumed origin
+  | -- | The origin derived by the client does match any of the assumed origins
     AuthenticationOriginMismatch
-      { -- | The origin explicitly passed to the `verifyAuthenticationResponse`
+      { -- | The origins explicitly passed to the `verifyAuthenticationResponse`
         -- response, set by the RP
-        aeExpectedOrigin :: M.Origin,
+        aeExpectedOrigins :: NonEmpty M.Origin,
         -- | The origin received from the client as part of the client data
         aeReceivedOrigin :: M.Origin
       }
@@ -157,11 +158,32 @@
 
 -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion)
 -- Verifies a 'M.Credential' response for an [authentication ceremony](https://www.w3.org/TR/webauthn-2/#authentication).
+--
 -- The 'arSignatureCounterResult' field of the result should be inspected to
 -- enforce Relying Party policy regarding potentially cloned authenticators.
+--
+-- Though this library implements the WebAuthn L2 spec, for origin validation we
+-- follow the L3 draft. This is because allowing multiple origins is often
+-- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/tr/webauthn-3/#sctn-validating-origin) 
+-- more details.
+-- In the simplest case, just a single origin is allowed and this is the 'M.RpId' with @https://@ prepended:
+--
+-- > verifyAuthenticationResponse (NE.singleton (M.Origin "https://example.org")) ...
+--
+-- In the more complex case, multiple origins are allowed:
+--
+-- > verifyAuthenticationResponse (M.Origin <$> "https://example.org" :| ["https://signin.example.org"]) ...
+--
+-- One might also allow native apps to authenticate:
+--
+-- > verifyAuthenticationResponse (M.Origin <$> "https://example.org" :| ["ios:bundle-id:org.example.ourapp"]) ...
+--
+-- See Apple's documentation on [associated domains](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys/)
+-- and Google's documentation on [Digital Asset Links](https://developers.google.com/identity/passkeys/developer-guides) for more information on how to link app
+-- origins to your Relying Party ID.
 verifyAuthenticationResponse ::
-  -- | The origin of the server
-  M.Origin ->
+  -- | The list of allowed origins for the ceremony
+  NonEmpty M.Origin ->
   -- | The hash of the relying party id
   M.RpIdHash ->
   -- | The user handle, in case the user is identified already
@@ -179,7 +201,7 @@
   -- Or in case of success a signature counter result, which should be dealt
   -- with
   Validation (NonEmpty AuthenticationError) AuthenticationResult
-verifyAuthenticationResponse origin rpIdHash midentifiedUser entry options credential = do
+verifyAuthenticationResponse origins rpIdHash midentifiedUser entry options credential = do
   -- 1. Let options be a new PublicKeyCredentialRequestOptions structure
   -- configured to the Relying Party's needs for the ceremony.
   -- NOTE: Implemented by caller
@@ -290,9 +312,11 @@
       AuthenticationChallengeMismatch (M.coaChallenge options) (M.ccdChallenge c)
 
   -- 13. Verify that the value of C.origin matches the Relying Party's origin.
-  unless (M.ccdOrigin c == origin) $
+  -- NOTE: We follow the L3 draft of the spec here, which allows for multiple origins.
+  -- https://www.w3.org/TR/webauthn-3/#rp-op-verifying-assertion-step-origin
+  unless (M.ccdOrigin c `elem` NE.toList origins) $
     failure $
-      AuthenticationOriginMismatch origin (M.ccdOrigin c)
+      AuthenticationOriginMismatch origins (M.ccdOrigin c)
 
   -- 14. Verify that the value of C.tokenBinding.status matches the state of
   -- Token Binding for the TLS connection over which the attestation was
diff --git a/src/Crypto/WebAuthn/Operation/Registration.hs b/src/Crypto/WebAuthn/Operation/Registration.hs
--- a/src/Crypto/WebAuthn/Operation/Registration.hs
+++ b/src/Crypto/WebAuthn/Operation/Registration.hs
@@ -68,11 +68,11 @@
         -- | The challenge received from the client, part of the response
         reReceivedChallenge :: M.Challenge
       }
-  | -- | The returned origin does not match the relying party's origin
+  | -- | The returned origin does not match any of the the relying party's origins
     RegistrationOriginMismatch
-      { -- | The origin explicitly passed to the `verifyRegistrationResponse`
+      { -- | The origins explicitly passed to the `verifyRegistrationResponse`
         -- response, set by the RP
-        reExpectedOrigin :: M.Origin,
+        reExpectedOrigins :: NonEmpty M.Origin,
         -- | The origin received from the client as part of the client data
         reReceivedOrigin :: M.Origin
       }
@@ -264,13 +264,35 @@
 deriving instance ToJSON RegistrationResult
 
 -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-registering-a-new-credential)
+-- Verifies a 'M.Credential' response for a [registration ceremony](https://www.w3.org/TR/webauthn-2/#registration-ceremony). 
+--
 -- The resulting 'rrEntry' of this call should be stored in a database by the
 -- Relying Party. The 'rrAttestationStatement' contains the result of the
 -- attempted attestation, allowing the Relying Party to reject certain
 -- authenticators/attempted entry creations based on policy.
+--
+-- Though this library implements the WebAuthn L2 spec, for origin validation we
+-- follow the L3 draft. This is because allowing multiple origins is often
+-- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) 
+-- more details.
+-- In the simplest case, just a single origin is allowed and this is the 'M.RpId' with @https://@ prepended:
+--
+-- > verifyRegistrationResponse (NE.singleton (M.Origin "https://example.org")) ...
+--
+-- In the more complex case, multiple origins are allowed:
+--
+-- > verifyRegistrationResponse (M.Origin <$> "https://example.org" :| ["https://signin.example.org"]) ...
+--
+-- One might also allow native apps to authenticate:
+--
+-- > verifyRegistrationResponse (M.Origin <$> "https://example.org" :| ["ios:bundle-id:org.example.ourapp"]) ...
+--
+-- See Apple's documentation on [associated domains](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys/)
+-- and Google's documentation on [Digital Asset Links](https://developers.google.com/identity/passkeys/developer-guides) for more information on how to link app
+-- origins to your Relying Party ID.
 verifyRegistrationResponse ::
-  -- | The origin of the server
-  M.Origin ->
+  -- | The list of allowed origins for the ceremony
+  NonEmpty M.Origin ->
   -- | The relying party id
   M.RpIdHash ->
   -- | The metadata registry, used for verifying the validity of the
@@ -287,7 +309,7 @@
   -- Or () in case of a result.
   Validation (NonEmpty RegistrationError) RegistrationResult
 verifyRegistrationResponse
-  rpOrigin
+  origins
   rpIdHash
   registry
   currentTime
@@ -349,9 +371,11 @@
           RegistrationChallengeMismatch corChallenge (M.ccdChallenge c)
 
       -- 9. Verify that the value of C.origin matches the Relying Party's origin.
-      unless (rpOrigin == M.ccdOrigin c) $
+      -- NOTE: We follow the L3 draft of the spec here, which allows for multiple origins.
+      -- https://www.w3.org/TR/webauthn-3/#rp-op-registering-a-new-credential-step-origin
+      unless (M.ccdOrigin c `elem` NE.toList origins) $
         failure $
-          RegistrationOriginMismatch rpOrigin (M.ccdOrigin c)
+          RegistrationOriginMismatch origins (M.ccdOrigin c)
 
       -- 10. Verify that the value of C.tokenBinding.status matches the state of
       -- Token Binding for the TLS connection over which the assertion was
diff --git a/tests/Emulation.hs b/tests/Emulation.hs
--- a/tests/Emulation.hs
+++ b/tests/Emulation.hs
@@ -37,7 +37,7 @@
 import Emulation.Client.Arbitrary ()
 import Spec.Util (predeterminedDateTime)
 import Test.Hspec (SpecWith, describe, it, shouldSatisfy)
-import Test.QuickCheck (property)
+import Test.QuickCheck (property, (==>))
 
 -- | Custom type to combine the MonadPseudoRandom with the Except monad. We
 -- force the ChaChaDRG to ensure the App type is completely pure, and
@@ -61,12 +61,13 @@
 register ::
   (Random.MonadRandom m, MonadFail m) =>
   AnnotatedOrigin ->
+  NE.NonEmpty M.Origin ->
   UserAgentConformance ->
   Authenticator ->
   Meta.MetadataServiceRegistry ->
   DateTime ->
   m (Either (NE.NonEmpty O.RegistrationError) O.RegistrationResult, Authenticator, M.CredentialOptions 'M.Registration)
-register ao conformance authenticator registry now = do
+register ao allowedOrigins conformance authenticator registry now = do
   -- Generate new random input
   assertionChallenge <- M.generateChallenge
   userId <- M.generateUserHandle
@@ -84,7 +85,7 @@
   let registerResult =
         toEither $
           O.verifyRegistrationResponse
-            (aoOrigin ao)
+            allowedOrigins
             (M.RpIdHash . hash . encodeUtf8 . M.unRpId $ aoRpId ao)
             registry
             now
@@ -95,11 +96,12 @@
 login ::
   (Random.MonadRandom m, MonadFail m) =>
   AnnotatedOrigin ->
+  NE.NonEmpty M.Origin ->
   UserAgentConformance ->
   Authenticator ->
   O.CredentialEntry ->
   m (Either (NE.NonEmpty O.AuthenticationError) O.SignatureCounterResult)
-login ao conformance authenticator ce@O.CredentialEntry {..} = do
+login ao allowedOrigins conformance authenticator ce@O.CredentialEntry {..} = do
   attestationChallenge <- M.generateChallenge
   let options = defaultCog attestationChallenge
   -- Perform client assertion emulation with the same authenticator, this
@@ -109,7 +111,7 @@
     . second O.arSignatureCounterResult
     . toEither
     $ O.verifyAuthenticationResponse
-      (aoOrigin ao)
+      allowedOrigins
       (M.RpIdHash . hash . encodeUtf8 . M.unRpId $ aoRpId ao)
       (Just ceUserHandle)
       ce
@@ -118,27 +120,60 @@
 
 spec :: SpecWith ()
 spec =
-  describe "None" $
+  describe "None" $ do
+    it "rejects unknown origin during registration" $  do
+      property $ \seed authenticator allowedOrigins' origin' -> not (null allowedOrigins') && origin' `notElem` allowedOrigins' ==> do
+        let origin = M.Origin origin'
+        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'
+        let annotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = origin }
+        let registry = mempty
+        let userAgentConformance = mempty
+        let Right (registerResult, _, _) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)
+        registerResult `shouldSatisfy` \case
+          Left errors -> any (\case O.RegistrationOriginMismatch _ _ -> True; _ -> False) errors
+          Right _ -> False
+    it "rejects unknown origin during login" $ do
+      property $ \seed authenticator allowedOrigins' origin' -> not (null allowedOrigins') && origin' `notElem` allowedOrigins'  ==> do
+        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'
+        let origin = NE.head allowedOrigins
+        let wrongOrigin = M.Origin origin'
+        let annotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = origin }
+        let wrongAnnotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = wrongOrigin }
+        let registry = mempty
+        let userAgentConformance = mempty
+        let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)
+        let registerResult' = second O.rrEntry registerResult
+        registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options
+        case registerResult' of
+          Right credentialEntry -> do
+            let Right loginResult = runApp (seed + 1) (login wrongAnnotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)
+            loginResult `shouldSatisfy` \case
+              Left errors -> any (\case O.AuthenticationOriginMismatch _ _ -> True; _ -> False) errors
+              Right _ -> False
+          _ -> pure ()
+
     it "succeeds" $
-      property $ \seed authenticator userAgentConformance -> do
+      property $ \seed authenticator userAgentConformance  allowedOrigins' -> length allowedOrigins' > 1 ==> do
+        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'
         let annotatedOrigin =
               AnnotatedOrigin
                 { aoRpId = M.RpId "localhost",
-                  aoOrigin = M.Origin "https://localhost:8080"
+                  aoOrigin = NE.head allowedOrigins
                 }
+        
         -- Since our emulator only supports None attestation the registry can be left empty.
         let registry = mempty
         -- We are not currently interested in client or authenticator fails, we
         -- only wish to test our relying party implementation and are thus only
         -- interested in its errors.
-        let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin userAgentConformance authenticator registry predeterminedDateTime)
+        let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)
         -- Since we only do None attestation, we only care about the resulting entry
         let registerResult' = second O.rrEntry registerResult
         registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options
         -- Only if attestation succeeded can we continue with assertion
         case registerResult' of
           Right credentialEntry -> do
-            let Right loginResult = runApp (seed + 1) (login annotatedOrigin userAgentConformance authenticator' credentialEntry)
+            let Right loginResult = runApp (seed + 1) (login annotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)
             loginResult `shouldSatisfy` validAssertionResult authenticator userAgentConformance
           _ -> pure ()
 
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -80,7 +80,7 @@
   let registerResult =
         toEither $
           O.verifyRegistrationResponse
-            origin
+            (NE.singleton origin)
             (M.RpIdHash . hash . encodeUtf8 . M.unRpId $ rpId)
             service
             now
@@ -125,7 +125,7 @@
             registerResult =
               toEither $
                 O.verifyRegistrationResponse
-                  (M.Origin "http://localhost:8080")
+                  (NE.singleton $ M.Origin "http://localhost:8080")
                   (M.RpIdHash . hash $ ("localhost" :: ByteString.ByteString))
                   registry
                   predeterminedDateTime
@@ -142,7 +142,7 @@
             signInResult =
               toEither $
                 O.verifyAuthenticationResponse
-                  (M.Origin "http://localhost:8080")
+                  (NE.singleton $ M.Origin "http://localhost:8080")
                   (M.RpIdHash . hash $ ("localhost" :: ByteString.ByteString))
                   (Just (M.UserHandle "UserId"))
                   credentialEntry
@@ -163,7 +163,7 @@
             registerResult =
               toEither $
                 O.verifyRegistrationResponse
-                  (M.Origin "http://localhost:8080")
+                  (NE.singleton $ M.Origin "http://localhost:8080")
                   (M.RpIdHash . hash $ ("localhost" :: ByteString.ByteString))
                   registry
                   predeterminedDateTime
@@ -180,7 +180,7 @@
             signInResult =
               toEither $
                 O.verifyAuthenticationResponse
-                  (M.Origin "http://localhost:8080")
+                  (NE.singleton $ M.Origin "http://localhost:8080")
                   (M.RpIdHash . hash $ ("localhost" :: ByteString.ByteString))
                   (Just (M.UserHandle "UserId"))
                   credentialEntry
diff --git a/webauthn.cabal b/webauthn.cabal
--- a/webauthn.cabal
+++ b/webauthn.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: webauthn
-version: 0.9.0.0
+version: 0.10.0.0
 license: Apache-2.0
 license-file: LICENSE
 copyright:
