This is a SAML 2.0 Clojure library for SSO acting as a fairly thin wrapper around the Java libraries OpenSAML v3 and some utility functions from OneLogin's SAML library This library allows a Clojure application to act as a Service Provider (SP).
In order for an identityprovider to understand you as a service-provider, you need to provide metadata about your service. This is done in the following manner:
(in-ns my-saml.core
(:require [saml20-clj.core :as saml-core]
[saml20-clj.coerce :as saml-coerce]))
(def config {:app-name "My Fancy App"
:acs-url "https://my-app.com/saml/login"
:slo-url "https://my-app.com/saml/logout"}
(def credentials {:alias "my-saml-secrets"
:filename "path/to/keystorefile.jks"
:password "s1krit"}
(def metadata (-> {:sp-cert (saml-coerce/->X509Certificate credentials)}
(merge config)
saml-core/metadata)
You can keep track of which requests are in flight to determine whether responses correspond to valid requests we've
issued and whether we've already got a response for a request (e.g. to replay attacks) by using a StateManager
. This
library ships with a simple in-memory state manager suitable for a single instance, but you can create your own
implementation if you need something more sophisticated.
(require '[saml20-clj.core :as saml])
(def state-manager (saml/in-memory-state-manager))
Basic usage for requests to the IdP looks like:
(require '[saml20-clj.core :as saml])
;; create a new request
(-> (saml/request
{:sp-name "My SP Name"
:acs-url "http://sp.example.com/demo1/index.php?acs"
:idp-url "http://idp.example.com/SSOService.php"
:issuer "http://sp.example.com/demo1/metadata.php"
;; state manager (discussed above) is optional, but if passed `request` will record the newly created request.
:state-manager state-manager
;; :credential is optional. If passed, sign the request with this key and attach public key data, if present
:credential sp-private-key})
;; create a Ring redirect response to the IDP URL; pass the request as base-64 encoded `SAMLRequest` query parameter
(saml/idp-redirect-response "http://idp.example.com/SSOService.php"
;; This is RelayState. In the old version of the lib it was encrypted. In some cases,
;; like this it's not really sensitive so it doesn't need to be encrypted. Adding
;; automatic encryption support back is on the TODO list
"http://sp.example.com/please/redirect/me/to/here"))
The :credential
can be used to sign the request to the IdP, and attach any public key information (if present). It will happily accept several formats, depending on the use-case:
private-key
: A PEM formatted string[public-cert private-key]
: A tuple containing an X509 Certificate and a private key, both in PEM format{:filepath "/path/to/keystore" :password "keystore-password" :alias "key-alias"}
: A map describing a keystore and alias used.Basic usage for responses from the IdP looks like this (assuming a Ring request
):
(require '[saml20-clj.core :as saml])
(require '[saml20-clj.encode-decode :as saml-decode])
(-> request
:params
:SAML-response
saml-decode/base64->str
;; Coerce the response to an OpenSAML `Response`. This can be anything from a raw XML string to a parsed
;; `org.w3c.dom.Document`
saml/->Response
;; decrypt and validate the response. Returns decrypted response
(saml/validate idp-cert sp-private-key options)
;; convert the Assertions to a convenient Clojure map so you can do something with them
saml/assertions)
validate
accepts several options that let you configure what validations are done. The default options are:
{ ;; e.g. "http://sp.example.com/demo1/index.php?acs" The assertion consumer service URL. If this is not-nil, the
;; :recipient validator checks that <SubjectConfirmationData> nodes have a value of Recipient matching this value.
:acs-url nil
;; The ID of the request we (the SP) sent to the IdP. ID is generated on our end, and should be something like a UUID
;; rather than a sequential number. If non-nil, the :in-response-to validator checks that <SubjectConfirmationData>
;; nodes have a value of InResponseTo that matches an ID.
;;
;; The state manager implementation that ships with this library does not keep request state; InResponseTo validation
;; is provided as an option in case you write your own more sophisticated implementation.
:request-id nil
;; If passed, the state manager will
:state-manager
;; whether this response was solicited (i.e., in response to a request we sent to the IdP). If this is false, the
;; :in-response-to validator checks that the request-id is nil.
:solicited? true
;; maximum amount of clock skew to allow for the :not-on-or-after and :not-before validators
:allowable-clock-skew-seconds 180
;; address of the client. If set, the :address validator will check that <SubjectConfirmationData> nodes have an
;; Address matching this value *iff* Address is present. Address is optional attribute.
:user-agent-address nil
;; :response-validators and :assertion-validators are validation functions that run and check that the Response is
;; valid. If a check fails, these methods will throw an Exception. You can exclude some of these validators or add
;; your own by passing different values for these keys. Both types of validators are defined as multimethods; you can
;; add custom validators by adding more method implementations to their respective multimethods.
;; :response-validators are validation functions that run once for the entire Response. They are defined as
;; implementations of the saml20-clj.sp.response/validate-response multimethod.
:response-validators
;; The default Response validators are:
[;; If the <Response> itself is signed, verifies that this signature is matches the Response itself and matches the
;; IdP certificate. If Response is not signed, this validator does nothing.
:signature
;; requires that either the <Response> is signed, *every* <Assertion> is signed.
:require-signature
;; validates the request ID with :state-manager if it is passed as an option. This does not validate that the value
;; matches InResponseTo -- that is done by :in-response-to.
:valid-request-id]
;; :assertion validators are validation functions that run against every Assertion in the response. They are defined
;; as implementations of saml20-clj.sp.response/validate-assertion.
:assertion-validators
;; The default Assertion validators are:
[;; If <Assertion> is signed, the signature matches the Assertion and the IdP certificate. If <Assertion> is not
;; signed, this validator does nothing.
:signature
;; If :acs-url is non-nil, and <SubjectConfirmationData> is present, checks that <SubjectConfirmationData> has a
;; Recipient attribute matching this value.
:recipient
;; If <SubjectConfirmationData> is present, has a NotOnOrAfter attribute, and its value is in the future,
;; accounting for :allowable-clock-skew-seconds
:not-on-or-after
;; If <SubjectConfirmationData> has a NotBefore attribute, checks that this value is in the past, accounting for
;; :allowable-clock-skew-seconds
:not-before
;; If :request-id is non-nil and <SubjectConfirmationData> is present, checks that <SubjectConfirmationData> has an
;; InResponseTo attribute matching :request-id.
:in-response-to
;; If :user-agent-address is non-nil and <SubjectConfirmationData> has an Address attribute, checks that Address
;; matches this value.
:address]}
saml20-clj
libraryThis repository is forked from vlacs/saml20-clj, and at this point is more or less a complete re-write.
saml20-clj.shared/base64->inflate->str
not actually calling byte-inflate
at all<Assertion>
signatures not being validatedDistributed under the Eclipse Public License, the same as Clojure.
Can you improve this documentation? These fine people already did:
Cam Saul, Jon Doane, Kenji Nakamura, Erik Assum, David Russell & Matt OquistEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close