wai-saml2
SAML2 assertion validation as WAI middleware
https://github.com/mbg/wai-saml2#readme
LTS Haskell 24.3: | 0.6 |
Stackage Nightly 2025-08-11: | 0.7.0 |
Latest on Hackage: | 0.7.0 |
wai-saml2-0.7.0@sha256:07beba85d4d3afb3cad26bbdd6b4446546f576f705c3a3423e8ee1c4defe1165,3560
Module documentation for 0.7.0
- Network
- Network.Wai
- Network.Wai.SAML2
- Network.Wai.SAML2.Assertion
- Network.Wai.SAML2.C14N
- Network.Wai.SAML2.Config
- Network.Wai.SAML2.EntityDescriptor
- Network.Wai.SAML2.Error
- Network.Wai.SAML2.KeyInfo
- Network.Wai.SAML2.NameIDFormat
- Network.Wai.SAML2.Request
- Network.Wai.SAML2.Response
- Network.Wai.SAML2.Signature
- Network.Wai.SAML2.StatusCode
- Network.Wai.SAML2.Validation
- Network.Wai.SAML2.XML
- Network.Wai.SAML2
- Network.Wai
wai-saml2
A Haskell library which implements SAML2 assertion validation as WAI middleware. This can be used by a Haskell web application (the service provider, SP) to perform identity provider (IdP) initiated authentication, i.e. SAML2-based authentication where the authentication begins at the IdP-end, the IdP authenticates the user, and then gets the user to submit a SAML2 assertion back to the SP (known as “unsolicited SSO” within e.g. the Shibboleth project).
Completeness
There are currently a number of limitations to this library:
-
The library implements IdP-initiated authentication and has some support for SP-initiated authentication (See the documentation for the
Network.Wai.SAML2.Request
module) -
The library does not currently support the full SAML2 specification and makes certain assumptions about what the IdP’s responses contain. It will most likely fail with any IdPs which do not send responses in the same format. If you wish to use this library and encounter problems with your IdP, please open an issue or a pull request which implements support accordingly.
Security
The library is estimated to be sufficiently robust for use in a production environment. If you wish to implement this middleware, please note the following:
-
You must store IDs of assertions you see. If an assertion is successfully validated by this library, you must check that you have not previously seen the assertion ID in order to prevent replay attacks.
-
You must not expose any errors to a client as that could severely compromise the security of the system as attackers may be able to use the errors to narrow down valid SAML responses. You should log and monitor errors though as they may indicate attacks on your system. Ensure that log files containing errors from the SAML2 middleware are stored securely.
Usage
Preliminaries
You need to have registered your service provider with the identity provider. You need to have access to the IdP’s metadata, which will contain the public key used for signature validation.
Configuration
How to configure this library depends on your IdP’s configuration. You should consult the relevant documentation for your IdP as well as review your SP’s configuration on the IdP end. You should almost certainly customise the configuration beyond the defaults described below.
The saml2Config
function may be used to construct SAML2Config
values. Configurations constructed with saml2Config
expect assertions to be encrypted. If you expect assertions to be unencrypted, then you may wish to start with saml2ConfigNoEncryption
instead.
Since the saml2Config
function expects encrypted assertions, it needs at least the SP’s private key and the IdP’s public key as arguments (even when mandatory encryption is disabled). The private and public keys can be loaded with functions from the Data.X509
and Data.X509.File
modules (from the x509
and x509-store
packages, respectively):
(saml2Config spPrivateKey idpPublicKey){
saml2AssertionPath = "/sso/assert",
saml2ExpectedIssuer = Just "https://idp.sp.com/saml2",
saml2ExpectedDestination = Just "https://example.com/sso/assert",
}
The configuration options (saml2AssertionPath
, saml2ExpectedIssuer
, saml2ExpectedDestination
, etc.) are documented in the Haddock documentation for the Network.Wai.SAML2.Config
module.
Both saml2Config
and saml2ConfigNoEncryption
construct configurations which validate only the response signature. If you need to validate the assertion signature, you must change the saml2ValidationTarget
property to ValidateAssertion
. This can also be set to ValidateEither
, which will require either the response signature or the assertion signature to be valid. Do not use ValidateEither
unless your IdP requires this.
Implementation
Two interfaces to the middleware are provided. See the Haddock documentation for the Network.Wai.SAML2
module for full usage examples. An example using the saml2Callback
variant is shown below, where cfg
is a SAML2Config
value and mainApp
is your existing WAI application:
saml2Callback cfg callback mainApp
where callback (Left err) app req sendResponse = do
-- a POST request was made to the assertion endpoint, but
-- something went wrong, details of which are provided by
-- the error: this should probably be logged as it may
-- indicate that an attack was attempted against the
-- endpoint, but you *must* not show the error
-- to the client as it would severely compromise
-- system security
--
-- you may also want to return e.g. a HTTP 400 or 401 status
callback (Right result) app req sendResponse = do
-- a POST request was made to the assertion endpoint and the
-- SAML2 response was successfully validated:
-- you *must* check that you have not encountered the
-- assertion ID before; we assume that there is a
-- computation tryRetrieveAssertion which looks up
-- assertions by ID in e.g. a database
result <- tryRetrieveAssertion (assertionId (assertion result))
case result of
Just something -> -- a replay attack has occurred
Nothing -> do
-- store the assertion id somewhere
storeAssertion (assertionId (assertion result))
-- the assertion is valid and you can now e.g.
-- retrieve user data from your database
-- before proceeding with the request by e.g.
-- redirecting them to the main view
Contributions
Please see CONTRIBUTING.md
References
- SAML2 specification
- Exclusive XML Canonicalisation
- XML Signature Syntax and Processing
- XML Encryption Syntax and Processing
Changes
Changelog for wai-saml2
0.7
- Replaced
x509Certificate
withx509Certificates
inIDPSSODescriptor
so that it may have more than one certificate (#65 by @fumieval) - Added
attributeValues
toAssertionAttribute
in order to handle multiple attribute values with the same name (#67 by @fumieval) - Support signed assertions, not just signed responses (#45 by @fumieval)
- Fixed a bug that could cause
renderXML
to crash (#66 by @fumieval) - Test more Stack resolvers in CI (#71 by @kushagarr)
0.6
0.5
- Support GHC 9.6 (#53 by @mbg)
- Fixed a bug in XML canonicalisation causing a digest mismatch on Okta when assertion attributes are present (special thanks to @hiroqn) (#51 by @fumieval)
- Added
authnRequestDestination
field toAuthnRequest
(#47 by @Philonous)
0.4
- Split
validateResponse
intodecodeResponse
andvalidateSAMLResponse
(#31 by @fumieval) - Exported
NameID
(formerlyNameId
), and renamedsubjectNameId
tosubjectNameID
- Support GHC 9.4 (#36 by @mbg)
- Add new module
Network.Wai.SAML2.Request
withAuthnRequest
generation for SP-initiated login flow (#19 by @fumieval) - Changed the
saml2PrivateKey
field to be optional and addedsaml2ConfigNoEncryption
which takes aPublicKey
only (#37 by @fumieval) - Added
showUTCTime
toNetwork.Wai.SAML2.XML
- Added a new module
Network.Wai.SAML2.NameIDFormat
(#21 by @fumieval) - Added new field
response
toResult
which contains the full, decoded SAML response (#33 by @Philonous) - Validate audience restrictions (#35 by @Philonous)
- Handle status codes according to the SAML2 specification (#42) by @mbg
0.3
- Improve parse error handling and make
encryptedKeyData
optional (#11 by @Philonous) - Add
subjectNameId
toSubject
type (#13 by @kdxu) - Support the response format used by Okta, in which the
EncryptedAssertion
element is structured differently (#12 by @fumieval)
0.2.1.3
- Metadata updates.
0.2.1.2
No changes.
0.2.1.1
- Export
Result
type fromNetwork.Wai.SAML2
module.
0.2.1
- Fix missing export of
relayStateKey
and change its type.
0.2.0
- Added parsing for RelayState from form data, as sent by e.g. Shibboleth when a
target
query string parameter is passed to the unsolicited SSO endpoint.
0.1.0
- Initial release