diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog for `wai-saml2`
 
+## Unreleased changes
+
+## 0.5
+
+-   Support GHC 9.6 ([#53](https://github.com/mbg/wai-saml2/pull/53) by [@mbg](https://github.com/mbg))
+-   Fixed a bug in XML canonicalisation causing a digest mismatch on Okta when assertion attributes are present (special thanks to @hiroqn) ([#51](https://github.com/mbg/wai-saml2/pull/51) by [@fumieval](https://github.com/fumieval))
+-   Added `authnRequestDestination` field to `AuthnRequest` ([#47](https://github.com/mbg/wai-saml2/pull/47) by [@Philonous](https://github.com/Philonous))
+
 ## 0.4
 
 -   Split `validateResponse` into `decodeResponse` and `validateSAMLResponse` ([#31](https://github.com/mbg/wai-saml2/pull/31) by [@fumieval](https://github.com/fumieval))
diff --git a/src/Network/Wai/SAML2/C14N.hs b/src/Network/Wai/SAML2/C14N.hs
--- a/src/Network/Wai/SAML2/C14N.hs
+++ b/src/Network/Wai/SAML2/C14N.hs
@@ -14,6 +14,8 @@
 --------------------------------------------------------------------------------
 
 import qualified Data.ByteString as BS
+import Data.Text (Text)
+import qualified Data.Text.Encoding as T
 
 import Foreign.C.Types
 
@@ -21,9 +23,10 @@
 
 --------------------------------------------------------------------------------
 
--- | 'canonicalise' @xml@ produces a canonical representation of @xml@.
-canonicalise :: BS.ByteString -> IO BS.ByteString
-canonicalise xml = c14n c14nOpts c14n_exclusive_1_0 [] False Nothing xml
+-- | 'canonicalise' @prefixList@ @xml@ produces a canonical representation of @xml@
+-- while retaining namespaces matching @prefixList@.
+canonicalise :: [Text] -> BS.ByteString -> IO BS.ByteString
+canonicalise prefixList xml = c14n c14nOpts c14n_exclusive_1_0 (map T.encodeUtf8 prefixList) False Nothing xml
 
 -- | The options we want to use for canonicalisation of XML documents.
 c14nOpts :: [CInt]
diff --git a/src/Network/Wai/SAML2/Request.hs b/src/Network/Wai/SAML2/Request.hs
--- a/src/Network/Wai/SAML2/Request.hs
+++ b/src/Network/Wai/SAML2/Request.hs
@@ -63,6 +63,11 @@
     ,   authnRequestID :: !T.Text
         -- | SP Entity ID
     ,   authnRequestIssuer :: !T.Text
+        -- | The URI reference to which this request is to be sent. Required
+        -- for signed requests
+        --
+        -- @since 0.5
+    ,   authnRequestDestination :: !(Maybe T.Text)
         -- | Allow IdP to generate a new identifier
     ,   authnRequestAllowCreate :: !Bool
         -- | The URI reference corresponding to a name identifier format
@@ -83,6 +88,7 @@
     pure AuthnRequest{
         authnRequestAllowCreate = True
     ,   authnRequestNameIDFormat = Transient
+    ,   authnRequestDestination = Nothing
     ,   ..
     }
 
@@ -118,13 +124,15 @@
         root = Element
             (saml2pName "AuthnRequest")
             (Map.fromList
-                [ ("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
+               ([ ("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
                 , ("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
                 , ("ID", authnRequestID) -- Reference [RequestAbstractType] and see [ID Values]
                 , ("Version", "2.0")  -- [RequestAbstractType]
                 , ("IssueInstant", timestamp) -- [RequestAbstractType]
                 , ("AssertionConsumerServiceIndex", "1") -- [AuthnRequest]
-                ])
+                ]
+                -- [RequestAbstractType]
+                ++ [("Destination", uri) | let Just uri = authnRequestDestination] ))
             [NodeElement issuer, NodeElement nameIdPolicy]
         -- Reference [RequestAbstractType]
         issuer = Element
diff --git a/src/Network/Wai/SAML2/Response.hs b/src/Network/Wai/SAML2/Response.hs
--- a/src/Network/Wai/SAML2/Response.hs
+++ b/src/Network/Wai/SAML2/Response.hs
@@ -11,6 +11,7 @@
     Response(..),
     removeSignature,
     extractSignedInfo,
+    extractPrefixList,
 
     -- * Re-exports
     module Network.Wai.SAML2.StatusCode,
@@ -132,6 +133,18 @@
                            &/ element (dsName "SignedInfo")
                           ) >>= nodes
     pure signedInfo
+
+-- | Obtain a list of InclusiveNamespaces entries used for exclusive XML canonicalisation.
+--
+-- @since 0.5
+extractPrefixList :: Cursor -> [T.Text]
+extractPrefixList cursor = concatMap T.words
+    $ concatMap (attribute "PrefixList")
+    $ cursor
+    $/ element (dsName "Reference")
+    &/ element (dsName "Transforms")
+    &/ element (dsName "Transform")
+    &/ element (ecName "InclusiveNamespaces")
 
 --------------------------------------------------------------------------------
 
diff --git a/src/Network/Wai/SAML2/Validation.hs b/src/Network/Wai/SAML2/Validation.hs
--- a/src/Network/Wai/SAML2/Validation.hs
+++ b/src/Network/Wai/SAML2/Validation.hs
@@ -16,7 +16,9 @@
 --------------------------------------------------------------------------------
 
 import Control.Exception
+import Control.Monad (forM_, when, unless)
 import Control.Monad.Except
+import Control.Monad.IO.Class (liftIO)
 
 import Crypto.Error
 import Crypto.Hash
@@ -69,7 +71,7 @@
 
     -- try to parse the XML document; throw an exception if it is not
     -- a valid XML document
-    responseXmlDoc <- case XML.parseLBS def (LBS.fromStrict resXmlDocData) of
+    responseXmlDoc <- case XML.parseLBS parseSettings (LBS.fromStrict resXmlDocData) of
         Left err -> throwError $ InvalidResponseXml err
         Right responseXmlDoc -> pure responseXmlDoc
 
@@ -130,8 +132,9 @@
     let signedInfoXml = XML.renderLBS def doc
 
     -- canonicalise the textual representation of the SignedInfo element
+    let prefixList = extractPrefixList (XML.fromDocument doc)
     signedInfoCanonResult <- liftIO $ try $
-        canonicalise (LBS.toStrict signedInfoXml)
+        canonicalise prefixList (LBS.toStrict signedInfoXml)
 
     normalisedSignedInfo <- case signedInfoCanonResult of
         Left err -> throwError $ CanonicalisationFailure err
@@ -161,7 +164,7 @@
 
     -- then render the resulting document and canonicalise it
     let renderedXml = XML.renderLBS def docMinusSignature
-    refCanonResult <- liftIO $ try $ canonicalise (LBS.toStrict renderedXml)
+    refCanonResult <- liftIO $ try $ canonicalise prefixList (LBS.toStrict renderedXml)
 
     normalised <- case refCanonResult of
         Left err -> throwError $ CanonicalisationFailure err
diff --git a/src/Network/Wai/SAML2/XML.hs b/src/Network/Wai/SAML2/XML.hs
--- a/src/Network/Wai/SAML2/XML.hs
+++ b/src/Network/Wai/SAML2/XML.hs
@@ -13,6 +13,7 @@
     xencName,
     dsName,
     mdName,
+    ecName,
 
     -- * Utility functions
     toMaybeText,
@@ -21,7 +22,8 @@
 
     -- * XML parsing
     FromXML(..),
-    oneOrFail
+    oneOrFail,
+    parseSettings
 ) where
 
 --------------------------------------------------------------------------------
@@ -66,6 +68,14 @@
     Name name (Just "urn:oasis:names:tc:SAML:2.0:metadata") (Just "md")
 
 
+-- | 'ecName' @name@ constructs a 'Name' for @name@ in the
+-- http://www.w3.org/2001/10/xml-exc-c14n# namespace.
+--
+-- @since 0.5
+ecName :: T.Text -> Name
+ecName name =
+    Name name (Just "http://www.w3.org/2001/10/xml-exc-c14n#") (Just "ec")
+
 -- | 'toMaybeText' @xs@ returns 'Nothing' if @xs@ is the empty list, or
 -- the result of concatenating @xs@ wrapped in 'Just' otherwise.
 toMaybeText :: [T.Text] -> Maybe T.Text
@@ -100,3 +110,10 @@
 oneOrFail _ (x:_) = pure x
 
 --------------------------------------------------------------------------------
+
+-- | It is important to retain namespaces in order to calculate the hash of the canonicalised XML correctly.
+-- see: https://stackoverflow.com/questions/69252831/saml-2-0-digest-value-calculation-in-saml-assertion
+--
+-- @since 0.5
+parseSettings :: ParseSettings
+parseSettings = def { psRetainNamespaces = True }
diff --git a/tests/data/metadata/google.xml b/tests/data/metadata/google.xml
new file mode 100644
--- /dev/null
+++ b/tests/data/metadata/google.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://accounts.google.com/o/saml2?idpid=C01aa60hc" validUntil="2027-07-20T09:18:00.000Z">
+  <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+    <md:KeyDescriptor use="signing">
+      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:X509Data>
+          <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAYIgDLo9MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
+bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
+b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIwNzIx
+MDkxODAwWhcNMjcwNzIwMDkxODAwWjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
+TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA9KQNdYuN7oPUsJ1SK2L58egBELYIWr5cRd9PXuV1k1s2hzt2vMoPS1l788+/hVb4
+CtZCeabZDFYfNMbS2olbhG06FrS9+ptm2pKucXqEp+KM9FozmK/qn7+geQl7uHdcT0xRqfVK46Wr
+RB4S8VdbK9WIK2prCvHIPu1O9eH+m9g+G1Y116RIqKKGfGbsWdhyNY47+LcxXzThgdfi4BPwcB/i
+K/X/i4qNje4FLHrAJQ7S6bRiTXPyv7q9Md96R1T9hXdFA9siW9rmrJCg/H022TrqxwctlIkQjPGj
+yskF3vCcwuIj5M8PuP37k6AuiBZdV73gG8t8SgoMHhElWSLKowIDAQABMA0GCSqGSIb3DQEBCwUA
+A4IBAQDuIthaqITon7u3aZg70NutC54EsQ6NLfWsbwgadOahb5EtGnBkKB7bairnTSAMtH4wLnNj
+BrgxswnxVd5DdrmqpuoY+Pn3hAXYBCFwmAzEEYwmfkZbTVa7AcT6bw6nOqHrfnYump9LhUwxI4dV
+4Yr2YkLL767CeSMLv58GBlFAqxP63HVMa1BJAhZhjamqvSYQCJZYnBG7Nuy2ek6Y5kHQM/8L0h6n
+gx1Jv0nPvPI+9NlWRkUhDN/jQkzxKcYnCNlT/bi3xY+QjFWD4z/Qx2h9hrxZtLRtcrp6WqUmpXha
+dq1JsmSbXwRj4Tu7IVZ4tYqeY/mOJGYf8fr5h/Q8GRCd</ds:X509Certificate>
+        </ds:X509Data>
+      </ds:KeyInfo>
+    </md:KeyDescriptor>
+    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
+    <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://accounts.google.com/o/saml2/idp?idpid=C01aa60hc"/>
+    <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://accounts.google.com/o/saml2/idp?idpid=C01aa60hc"/>
+  </md:IDPSSODescriptor>
+</md:EntityDescriptor>
diff --git a/tests/data/metadata/google.xml.expected b/tests/data/metadata/google.xml.expected
new file mode 100644
--- /dev/null
+++ b/tests/data/metadata/google.xml.expected
@@ -0,0 +1,139 @@
+IDPSSODescriptor
+  { entityID = "https://accounts.google.com/o/saml2?idpid=C01aa60hc"
+  , x509Certificate =
+      SignedExact
+        { getSigned =
+            Signed
+              { signedObject =
+                  Certificate
+                    { certVersion = 2
+                    , certSerial = 1658395081277
+                    , certSignatureAlg = SignatureALG HashSHA256 PubKeyALG_RSA
+                    , certIssuerDN =
+                        DistinguishedName
+                          { getDistinguishedElements =
+                              [ ( [ 2 , 5 , 4 , 10 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google Inc."
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 7 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Mountain View"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 3 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 11 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google For Work"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 6 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "US"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 8 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "California"
+                                    }
+                                )
+                              ]
+                          }
+                    , certValidity =
+                        ( DateTime
+                            { dtDate =
+                                Date { dateYear = 2022 , dateMonth = July , dateDay = 21 }
+                            , dtTime =
+                                TimeOfDay
+                                  { todHour = 9 h , todMin = 18 m , todSec = 0 s , todNSec = 0 ns }
+                            }
+                        , DateTime
+                            { dtDate =
+                                Date { dateYear = 2027 , dateMonth = July , dateDay = 20 }
+                            , dtTime =
+                                TimeOfDay
+                                  { todHour = 9 h , todMin = 18 m , todSec = 0 s , todNSec = 0 ns }
+                            }
+                        )
+                    , certSubjectDN =
+                        DistinguishedName
+                          { getDistinguishedElements =
+                              [ ( [ 2 , 5 , 4 , 10 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google Inc."
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 7 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Mountain View"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 3 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 11 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "Google For Work"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 6 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "US"
+                                    }
+                                )
+                              , ( [ 2 , 5 , 4 , 8 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = Printable
+                                    , getCharacterStringRawData = "California"
+                                    }
+                                )
+                              ]
+                          }
+                    , certPubKey =
+                        PubKeyRSA
+                          PublicKey
+                            { public_size = 256
+                            , public_n =
+                                30883043751460212091009024715988672157201981026347726370499687818828606623610036063558065564910721575436562697595833895389078194628597753679300253553943785088191301337694287320462812204176082900979493095532403911400962461718281343090255859587662209446117641661982570706673532506066365285856542563187402675519172527708567282278848705855498817485769832020293045021199187525818891914876547302505893509597998904610940749090045911784327818251262543295743496580502018384973717126996683932142133838721711519234780192895538524052074852409104614561380839223725538723547399956034683375536115322508017066877882514789596315962019
+                            , public_e = 65537
+                            }
+                    , certExtensions = Extensions Nothing
+                    }
+              , signedAlg = SignatureALG HashSHA256 PubKeyALG_RSA
+              , signedSignature =
+                  "\238\"\216Z\168\132\232\159\187\183i\152;\208\219\173\v\158\EOT\177\SO\141-\245\172o\b\SUBt\230\161o\145-\SUBpd(\RS\219j*\231M \f\180~0.sc\ACK\184\&1\179\t\241U\222Cv\185\170\166\234\CAN\248\249\247\132\ENQ\216\EOT!p\152\f\196\DC1\140&~F[MV\187\SOH\196\250o\SO\167:\161\235~v.\154\159K\133L1#\135U\225\138\246bB\203\239\174\194y#\v\191\159\ACK\ACKQ@\171\DC3\250\220uLkPI\STX\SYNa\141\169\170\189&\DLE\b\150X\156\DC1\187\&6\236\182zN\152\230A\208\&3\255\v\210\RS\167\131\GSI\191I\207\188\242>\244\217VFE!\f\223\227BL\241)\198'\b\217S\253\184\183\197\143\144\140U\131\227?\208\199h}\134\188Y\180\180mr\186zZ\165&\165xZv\173I\178d\155_\EOTc\225;\187!Vx\181\138\158c\249\142$f\US\241\250\249\135\244<\EM\DLE\157"
+              }
+        , exactObjectRaw =
+            "0\130\STX\\\160\ETX\STX\SOH\STX\STX\ACK\SOH\130 \f\186=0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL0{1\DC40\DC2\ACK\ETXU\EOT\n\DC3\vGoogle Inc.1\SYN0\DC4\ACK\ETXU\EOT\a\DC3\rMountain View1\SI0\r\ACK\ETXU\EOT\ETX\DC3\ACKGoogle1\CAN0\SYN\ACK\ETXU\EOT\v\DC3\SIGoogle For Work1\v0\t\ACK\ETXU\EOT\ACK\DC3\STXUS1\DC30\DC1\ACK\ETXU\EOT\b\DC3\nCalifornia0\RS\ETB\r220721091800Z\ETB\r270720091800Z0{1\DC40\DC2\ACK\ETXU\EOT\n\DC3\vGoogle Inc.1\SYN0\DC4\ACK\ETXU\EOT\a\DC3\rMountain View1\SI0\r\ACK\ETXU\EOT\ETX\DC3\ACKGoogle1\CAN0\SYN\ACK\ETXU\EOT\v\DC3\SIGoogle For Work1\v0\t\ACK\ETXU\EOT\ACK\DC3\STXUS1\DC30\DC1\ACK\ETXU\EOT\b\DC3\nCalifornia0\130\SOH\"0\r\ACK\t*\134H\134\247\r\SOH\SOH\SOH\ENQ\NUL\ETX\130\SOH\SI\NUL0\130\SOH\n\STX\130\SOH\SOH\NUL\244\164\ru\139\141\238\131\212\176\157R+b\249\241\232\SOH\DLE\182\bZ\190\\E\223O^\229u\147[6\135;v\188\202\SIKY{\243\207\191\133V\248\n\214By\166\217\fV\US4\198\210\218\137[\132m:\SYN\180\189\250\155f\218\146\174qz\132\167\226\140\244Z3\152\175\234\159\191\160y\t{\184w\\OLQ\169\245J\227\165\171D\RS\DC2\241W[+\213\136+jk\n\241\200>\237N\245\225\254\155\216>\ESCV5\215\164H\168\162\134|f\236Y\216r5\142;\248\183\&1_4\225\129\215\226\224\DC3\240p\US\226+\245\255\139\138\141\141\238\ENQ,z\192%\SO\210\233\180bMs\242\191\186\189\&1\223zGT\253\133wE\ETX\219\"[\218\230\172\144\160\252}6\217:\234\199\a-\148\137\DLE\140\241\163\202\201\ENQ\222\240\156\194\226#\228\207\SI\184\253\251\147\160.\136\SYN]W\189\224\ESC\203|J\n\f\RS\DC1%Y\"\202\163\STX\ETX\SOH\NUL\SOH"
+        , encodeSignedObject =
+            "0\130\ETXt0\130\STX\\\160\ETX\STX\SOH\STX\STX\ACK\SOH\130 \f\186=0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL0{1\DC40\DC2\ACK\ETXU\EOT\n\DC3\vGoogle Inc.1\SYN0\DC4\ACK\ETXU\EOT\a\DC3\rMountain View1\SI0\r\ACK\ETXU\EOT\ETX\DC3\ACKGoogle1\CAN0\SYN\ACK\ETXU\EOT\v\DC3\SIGoogle For Work1\v0\t\ACK\ETXU\EOT\ACK\DC3\STXUS1\DC30\DC1\ACK\ETXU\EOT\b\DC3\nCalifornia0\RS\ETB\r220721091800Z\ETB\r270720091800Z0{1\DC40\DC2\ACK\ETXU\EOT\n\DC3\vGoogle Inc.1\SYN0\DC4\ACK\ETXU\EOT\a\DC3\rMountain View1\SI0\r\ACK\ETXU\EOT\ETX\DC3\ACKGoogle1\CAN0\SYN\ACK\ETXU\EOT\v\DC3\SIGoogle For Work1\v0\t\ACK\ETXU\EOT\ACK\DC3\STXUS1\DC30\DC1\ACK\ETXU\EOT\b\DC3\nCalifornia0\130\SOH\"0\r\ACK\t*\134H\134\247\r\SOH\SOH\SOH\ENQ\NUL\ETX\130\SOH\SI\NUL0\130\SOH\n\STX\130\SOH\SOH\NUL\244\164\ru\139\141\238\131\212\176\157R+b\249\241\232\SOH\DLE\182\bZ\190\\E\223O^\229u\147[6\135;v\188\202\SIKY{\243\207\191\133V\248\n\214By\166\217\fV\US4\198\210\218\137[\132m:\SYN\180\189\250\155f\218\146\174qz\132\167\226\140\244Z3\152\175\234\159\191\160y\t{\184w\\OLQ\169\245J\227\165\171D\RS\DC2\241W[+\213\136+jk\n\241\200>\237N\245\225\254\155\216>\ESCV5\215\164H\168\162\134|f\236Y\216r5\142;\248\183\&1_4\225\129\215\226\224\DC3\240p\US\226+\245\255\139\138\141\141\238\ENQ,z\192%\SO\210\233\180bMs\242\191\186\189\&1\223zGT\253\133wE\ETX\219\"[\218\230\172\144\160\252}6\217:\234\199\a-\148\137\DLE\140\241\163\202\201\ENQ\222\240\156\194\226#\228\207\SI\184\253\251\147\160.\136\SYN]W\189\224\ESC\203|J\n\f\RS\DC1%Y\"\202\163\STX\ETX\SOH\NUL\SOH0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL\ETX\130\SOH\SOH\NUL\238\"\216Z\168\132\232\159\187\183i\152;\208\219\173\v\158\EOT\177\SO\141-\245\172o\b\SUBt\230\161o\145-\SUBpd(\RS\219j*\231M \f\180~0.sc\ACK\184\&1\179\t\241U\222Cv\185\170\166\234\CAN\248\249\247\132\ENQ\216\EOT!p\152\f\196\DC1\140&~F[MV\187\SOH\196\250o\SO\167:\161\235~v.\154\159K\133L1#\135U\225\138\246bB\203\239\174\194y#\v\191\159\ACK\ACKQ@\171\DC3\250\220uLkPI\STX\SYNa\141\169\170\189&\DLE\b\150X\156\DC1\187\&6\236\182zN\152\230A\208\&3\255\v\210\RS\167\131\GSI\191I\207\188\242>\244\217VFE!\f\223\227BL\241)\198'\b\217S\253\184\183\197\143\144\140U\131\227?\208\199h}\134\188Y\180\180mr\186zZ\165&\165xZv\173I\178d\155_\EOTc\225;\187!Vx\181\138\158c\249\142$f\US\241\250\249\135\244<\EM\DLE\157"
+        }
+  , nameIDFormats =
+      [ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" ]
+  , singleSignOnServices =
+      [ ( HTTPRedirect
+        , "https://accounts.google.com/o/saml2/idp?idpid=C01aa60hc"
+        )
+      , ( HTTPPost
+        , "https://accounts.google.com/o/saml2/idp?idpid=C01aa60hc"
+        )
+      ]
+  }
diff --git a/tests/data/metadata/keycloak.xml b/tests/data/metadata/keycloak.xml
new file mode 100644
--- /dev/null
+++ b/tests/data/metadata/keycloak.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" entityID="https://sso.ja-sore.de/auth/realms/HERP">
+   <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+      <md:KeyDescriptor use="signing">
+         <ds:KeyInfo>
+            <ds:KeyName>AViu9Cf7_rjRHwqkm_MgvMcjWHuRPSP0mMNNEjHslaY</ds:KeyName>
+            <ds:X509Data>
+               <ds:X509Certificate>MIIClzCCAX8CBgF3TK79BzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARIRVJQMB4XDTIxMDEyOTA1NDYxMloXDTMxMDEyOTA1NDc1MlowDzENMAsGA1UEAwwESEVSUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANRBHQMqF2+g7CrESyzrv+HvwjvIdhdZWWc6z4GiD3zN1TE+cbh88JMUrVTWd7EJLq+9uAazCyZfzX5BdauDqsp+LP9z2xiJ6Zlao1z5ycfG4dwvEIlOv4rX1zSdtg35Cz+QdaUfIkCEX3dTHI7dFcwou+Nel0BGCnp17w8gVPOBGYzvOtBgWcNV9+9Zmn7oSRoG5R4uRzhmPv12pyKRO/0hZr6gTkQ11CsnyfW890bDM05I5m8gDT0fAO2PznbQIQKClqUoyNbJZbBeDyV7QaEkkC8Nckvo6y+bae8oEF9pxQiXBV7dHtY8VLWq5SN2IeX1VK59hMg2NdHUxjMZIiECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAzGn04cz8oaZoRUCo6OKhcwfOSrjojfqbUxW8ZAY6UPUV8IfAbaxMlc4j1NVq3awi+yCJWLDaZyOX4mQyD+NoHB5D1/qQi/wLBkg9TJt2wp6MjkOPrwkN8lvhabq8Us2NiBnhHzLjWawltie9r1W0pugRG4qSZvJqosJJh9Wr0m8MnaP28P7fp4J/j3UAivlGZvXXh1kcl/lRWhoYqmR3r2j98OWy/WdYfx/oPNDWMdJDdR9I/qtlOVHsiEDP8Z478/z9IYweOYYqg7Ou77Ys/5G6wRxRCwaUjMDPdFfyvfUBZ9EFpYUV1G/zXuGQwfuh27GWz8gytdd1vDxBU06h3w==</ds:X509Certificate>
+            </ds:X509Data>
+         </ds:KeyInfo>
+      </md:KeyDescriptor>
+      <md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml/resolve" index="0" />
+      <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
+      <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
+      <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
+      <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
+      <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+      <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.ja-sore.de/auth/realms/HERP/protocol/saml" />
+   </md:IDPSSODescriptor>
+</md:EntityDescriptor>
diff --git a/tests/data/metadata/keycloak.xml.expected b/tests/data/metadata/keycloak.xml.expected
new file mode 100644
--- /dev/null
+++ b/tests/data/metadata/keycloak.xml.expected
@@ -0,0 +1,89 @@
+IDPSSODescriptor
+  { entityID = "https://sso.ja-sore.de/auth/realms/HERP"
+  , x509Certificate =
+      SignedExact
+        { getSigned =
+            Signed
+              { signedObject =
+                  Certificate
+                    { certVersion = 0
+                    , certSerial = 1611899272455
+                    , certSignatureAlg = SignatureALG HashSHA256 PubKeyALG_RSA
+                    , certIssuerDN =
+                        DistinguishedName
+                          { getDistinguishedElements =
+                              [ ( [ 2 , 5 , 4 , 3 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = UTF8
+                                    , getCharacterStringRawData = "HERP"
+                                    }
+                                )
+                              ]
+                          }
+                    , certValidity =
+                        ( DateTime
+                            { dtDate =
+                                Date { dateYear = 2021 , dateMonth = January , dateDay = 29 }
+                            , dtTime =
+                                TimeOfDay
+                                  { todHour = 5 h , todMin = 46 m , todSec = 12 s , todNSec = 0 ns }
+                            }
+                        , DateTime
+                            { dtDate =
+                                Date { dateYear = 2031 , dateMonth = January , dateDay = 29 }
+                            , dtTime =
+                                TimeOfDay
+                                  { todHour = 5 h , todMin = 47 m , todSec = 52 s , todNSec = 0 ns }
+                            }
+                        )
+                    , certSubjectDN =
+                        DistinguishedName
+                          { getDistinguishedElements =
+                              [ ( [ 2 , 5 , 4 , 3 ]
+                                , ASN1CharacterString
+                                    { characterEncoding = UTF8
+                                    , getCharacterStringRawData = "HERP"
+                                    }
+                                )
+                              ]
+                          }
+                    , certPubKey =
+                        PubKeyRSA
+                          PublicKey
+                            { public_size = 256
+                            , public_n =
+                                26794629232301390559329251866062548351573676959105948670047115599484903764153173211872085488745203643849524181464124430648664560428395941550339690556064181777927415101879096439027796505139145202723504898654935482393722978364774771646139306434335595822802292206982564879345724045673514452796802353368762193429772204243864488315862304970769841689431483551389268024017379114959044330977741690398490550580701831871711628065022359440646669330356587937408829251700791982308190330679272353052846282673680829331107723412188694305482396334605009242290197499924909025290277992294341810791131897406148044363907966374411188642337
+                            , public_e = 65537
+                            }
+                    , certExtensions = Extensions Nothing
+                    }
+              , signedAlg = SignatureALG HashSHA256 PubKeyALG_RSA
+              , signedSignature =
+                  "\204i\244\225\204\252\161\166hE@\168\232\226\161s\a\206J\184\232\141\250\155S\NAK\188d\ACK:P\245\NAK\240\135\192m\172L\149\206#\212\213j\221\172\"\251 \137X\176\218g#\151\226d2\SI\227h\FS\RSC\215\250\144\139\252\v\ACKH=L\155v\194\158\140\142C\143\175\t\r\242[\225i\186\188R\205\141\136\EM\225\US2\227Y\172%\182'\189\175U\180\166\232\DC1\ESC\138\146f\242j\162\194I\135\213\171\210o\f\157\163\246\240\254\223\167\130\DEL\143u\NUL\138\249Ff\245\215\135Y\FS\151\249QZ\SUB\CAN\170dw\175h\253\240\229\178\253gX\DEL\US\232<\208\214\&1\210Cu\USH\254\171e9Q\236\136@\207\241\158;\243\252\253!\140\RS9\134*\131\179\174\239\182,\255\145\186\193\FSQ\v\ACK\148\140\192\207tW\242\189\245\SOHg\209\ENQ\165\133\NAK\212o\243^\225\144\193\251\161\219\177\150\207\200\&2\181\215u\188<ASN\161\223"
+              }
+        , exactObjectRaw =
+            "0\130\SOH\DEL\STX\ACK\SOHwL\174\253\a0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL0\SI1\r0\v\ACK\ETXU\EOT\ETX\f\EOTHERP0\RS\ETB\r210129054612Z\ETB\r310129054752Z0\SI1\r0\v\ACK\ETXU\EOT\ETX\f\EOTHERP0\130\SOH\"0\r\ACK\t*\134H\134\247\r\SOH\SOH\SOH\ENQ\NUL\ETX\130\SOH\SI\NUL0\130\SOH\n\STX\130\SOH\SOH\NUL\212A\GS\ETX*\ETBo\160\236*\196K,\235\191\225\239\194;\200v\ETBYYg:\207\129\162\SI|\205\213\&1>q\184|\240\147\DC4\173T\214w\177\t.\175\189\184\ACK\179\v&_\205~Au\171\131\170\202~,\255s\219\CAN\137\233\153Z\163\\\249\201\199\198\225\220/\DLE\137N\191\138\215\215\&4\157\182\r\249\v?\144u\165\US\"@\132_wS\FS\142\221\NAK\204(\187\227^\151@F\nzu\239\SI T\243\129\EM\140\239:\208`Y\195U\247\239Y\154~\232I\SUB\ACK\229\RS.G8f>\253v\167\"\145;\253!f\190\160ND5\212+'\201\245\188\247F\195\&3NH\230o \r=\US\NUL\237\143\206v\208!\STX\130\150\165(\200\214\201e\176^\SI%{A\161$\144/\rrK\232\235/\155i\239(\DLE_i\197\b\151\ENQ^\221\RS\214<T\181\170\229#v!\229\245T\174}\132\200\&65\209\212\198\&3\EM\"!\STX\ETX\SOH\NUL\SOH"
+        , encodeSignedObject =
+            "0\130\STX\151\&0\130\SOH\DEL\STX\ACK\SOHwL\174\253\a0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL0\SI1\r0\v\ACK\ETXU\EOT\ETX\f\EOTHERP0\RS\ETB\r210129054612Z\ETB\r310129054752Z0\SI1\r0\v\ACK\ETXU\EOT\ETX\f\EOTHERP0\130\SOH\"0\r\ACK\t*\134H\134\247\r\SOH\SOH\SOH\ENQ\NUL\ETX\130\SOH\SI\NUL0\130\SOH\n\STX\130\SOH\SOH\NUL\212A\GS\ETX*\ETBo\160\236*\196K,\235\191\225\239\194;\200v\ETBYYg:\207\129\162\SI|\205\213\&1>q\184|\240\147\DC4\173T\214w\177\t.\175\189\184\ACK\179\v&_\205~Au\171\131\170\202~,\255s\219\CAN\137\233\153Z\163\\\249\201\199\198\225\220/\DLE\137N\191\138\215\215\&4\157\182\r\249\v?\144u\165\US\"@\132_wS\FS\142\221\NAK\204(\187\227^\151@F\nzu\239\SI T\243\129\EM\140\239:\208`Y\195U\247\239Y\154~\232I\SUB\ACK\229\RS.G8f>\253v\167\"\145;\253!f\190\160ND5\212+'\201\245\188\247F\195\&3NH\230o \r=\US\NUL\237\143\206v\208!\STX\130\150\165(\200\214\201e\176^\SI%{A\161$\144/\rrK\232\235/\155i\239(\DLE_i\197\b\151\ENQ^\221\RS\214<T\181\170\229#v!\229\245T\174}\132\200\&65\209\212\198\&3\EM\"!\STX\ETX\SOH\NUL\SOH0\r\ACK\t*\134H\134\247\r\SOH\SOH\v\ENQ\NUL\ETX\130\SOH\SOH\NUL\204i\244\225\204\252\161\166hE@\168\232\226\161s\a\206J\184\232\141\250\155S\NAK\188d\ACK:P\245\NAK\240\135\192m\172L\149\206#\212\213j\221\172\"\251 \137X\176\218g#\151\226d2\SI\227h\FS\RSC\215\250\144\139\252\v\ACKH=L\155v\194\158\140\142C\143\175\t\r\242[\225i\186\188R\205\141\136\EM\225\US2\227Y\172%\182'\189\175U\180\166\232\DC1\ESC\138\146f\242j\162\194I\135\213\171\210o\f\157\163\246\240\254\223\167\130\DEL\143u\NUL\138\249Ff\245\215\135Y\FS\151\249QZ\SUB\CAN\170dw\175h\253\240\229\178\253gX\DEL\US\232<\208\214\&1\210Cu\USH\254\171e9Q\236\136@\207\241\158;\243\252\253!\140\RS9\134*\131\179\174\239\182,\255\145\186\193\FSQ\v\ACK\148\140\192\207tW\242\189\245\SOHg\209\ENQ\165\133\NAK\212o\243^\225\144\193\251\161\219\177\150\207\200\&2\181\215u\188<ASN\161\223"
+        }
+  , nameIDFormats =
+      [ "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+      , "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
+      , "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+      , "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
+      ]
+  , singleSignOnServices =
+      [ ( HTTPPost
+        , "https://sso.ja-sore.de/auth/realms/HERP/protocol/saml"
+        )
+      , ( HTTPRedirect
+        , "https://sso.ja-sore.de/auth/realms/HERP/protocol/saml"
+        )
+      , ( SOAP
+        , "https://sso.ja-sore.de/auth/realms/HERP/protocol/saml"
+        )
+      , ( HTTPArtifact
+        , "https://sso.ja-sore.de/auth/realms/HERP/protocol/saml"
+        )
+      ]
+  }
diff --git a/wai-saml2.cabal b/wai-saml2.cabal
--- a/wai-saml2.cabal
+++ b/wai-saml2.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.35.1.
+-- This file has been generated from package.yaml by hpack version 0.35.2.
 --
 -- see: https://github.com/sol/hpack
 
 name:           wai-saml2
-version:        0.4
+version:        0.5
 synopsis:       SAML2 assertion validation as WAI middleware
 description:    A Haskell library which implements SAML2 assertion validation as WAI middleware
 category:       Security
@@ -26,6 +26,10 @@
     tests/data/keycloak.xml.expected
     tests/data/okta.xml
     tests/data/okta.xml.expected
+    tests/data/metadata/google.xml
+    tests/data/metadata/google.xml.expected
+    tests/data/metadata/keycloak.xml
+    tests/data/metadata/keycloak.xml.expected
 
 source-repository head
   type: git
