Liking cljdoc? Tell your friends :D

saml20-clj

Downloads Dependencies Status Circle CI codecov License cljdoc badge

Clojars Project

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).

2.0.0 Usage

Creating metadata

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)

Recording Requests

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

Requests

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.

Responses

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]}

Differences from the original saml20-clj library

This repository is forked from vlacs/saml20-clj, and at this point is more or less a complete re-write.

  • Other improvements:
    • Uses OpenSAML v3 instead of OpenSAML v2 which was EOL'ed in 2016
    • Tons of bug fixes, such as saml20-clj.shared/base64->inflate->str not actually calling byte-inflate at all
    • Fixed millions of reflection warnings
    • Removed duplicate functions
    • Support for XML signing with SHA-256 instead of SHA-1, which is required by ADFS by default (via k2n/saml20-clj)
    • Support for Clojure 1.10+
    • Support for base-64 encodings that contain newlines
    • Removed lots of dependencies on other libraries
    • Reorganized code
    • Removed tons of duplicate/unnecessary, untested code
    • Fixed <Assertion> signatures not being validated

License

Distributed 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 Oquist
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close