UNPKG

wicked-saml

Version:
219 lines (158 loc) 9.09 kB
# wicked.haufe.io SAML SDK This library helps implementing Authorization Servers for federating SAML identites (SSO identities) into a wicked.haufe.io OAuth2.0 Implicit Grant Flow API implementation. It assumes your SAML IdP supports the HTTP-POST Binding and encrypted SAML Assertions. It does the heavy lifting regarding implementing a SAML SP (Service Provider) which talks to the IdP. You can find more information on wicked.haufe.io here: * [Official Website wicked.haufe.io](http://wicked.haufe.io) * [wicked.haufe.io Github repository](https://github.com/Haufe-Lexware/wicked.haufe.io) # Usage To install the SDK into your node.js application, run ```bash $ npm install wicked-saml --save --save-exact ``` Please note that you will also need to inject the `wicked-sdk` when initializing the `wicked-saml` SDK; check out the `wicked-sdk` NPM package for more information: [npmjs.com/packages/wicked-sdk](https://www.npmjs.com/package/wicked-sdk). The SDK will be kept downwards-compatible for as long as possible; it will be tried hard to make earlier versions of the SDK compatible with a later release of wicked.haufe.io, so using the `--save-exact` is a safe bet. ## Prerequisites The `wicked-saml` package is intended for use with ExpressJS 4.x and wicked.haufe.io. It is not intended for other types of usage. You SAML IdP needs to support the HTTP-POST-Binding and Encrypted Assertions. In some cases (like OpenAM), assertion encryption has to be **explicitly turned on** and cannot be part of the `metadata.xml`. If you receive errors static that `EncryptedAssertion` lengths is zero but was expected to be one, this may be the mistake. ## Example ```javascript var wicked = require('wicked-sdk'); var wickedSaml = require('wicked-saml') var async = require('async'); // another requirement for this sample async.series([ callback => wicked.initialize(callback), callback => wickedSaml.initialize(wicked, 'your-server-id', callback) ], function (err) { if (err) throw err; // or do whatever you need // start server }); ``` The single most interesting point is the string `'your-server-id'` in the above example. It relies on an an Authorization Server being registered in your wicked configuration. The `wicked-saml` will do the following thing: * Retrieve the `/auth-server/your-server-id` information from the API (that's what the `wicked-sdk`is needed for) * Read out the information in the `saml` properties and use that to initialize the implementation of the SAML Service Provider with that More information can be found in the wicked documentation under [Authorization Servers](https://github.com/Haufe-Lexware/wicked.haufe.io/blob/oauth2_implicit/doc/authorization-servers.md). A sample `your-server-id.json` file could look like this: ```json { "name": "your-server-id", "id": "your-server-id", "auth": "none", "desc": "Authorization Server for SAML Federation", "url": "https://${PORTAL_NETWORK_APIHOST}/auth-server/{{apiId}}?client_id=(your app's client id)", "config": { "api": { "upstream_url": "http://auth-server:3005", "request_path": "/auth-server" }, "plugins": [ { "config": { "header_name": "Correlation-Id", "generator": "uuid" }, "name": "correlation-id" } ] }, "saml": { "spOptions": { "entity_id": "https://api.company.com/auth-server/metadata.xml", "assert_endpoint": "https://api.company.com/auth-server/assert", "nameid_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "certificate": "-----BEGIN CERTIFICATE-----\nMIIDIDCCA...awot98FReb\n-----END CERTIFICATE-----", "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBA...Vy4HpO2KPg==\n-----END RSA PRIVATE KEY-----" }, "idpOptions": { "sso_login_url": "https://your-saml-idp.com:443/auth/SSORedirect/metaAlias/idp1", "certificates": [ "-----BEGIN CERTIFICATE-----\nMIICrTCC...g8t2tGs=\n-----END CERTIFICATE-----" ] } } } ``` The options which can be used can be found in the documentation of `saml2-js`, which is the library which is used "under the hood" of the `wicked-saml` package: [npmjs.com/package/saml2-js](https://www.npmjs.com/package/saml2-js). The example properties above (when correctly filled) will work with e.g. OpenAM. # Library description The following functions are exported by `wicked-saml`. ### `wickedSaml.initialize(wicked, serverId, callback)` Initialize the SAML library; calls the wicked API to retrieve information on the Authorization Server registration of the wicked configuration (see above). The `serverId` has to match the `auth-server` definition ID. Pass your `wicked-sdk` instance to the library here; `wicked-saml` will use its `apiGet` function. **Callback signature**: `function(err)` -- Does not return anything but an error, or `null` if successful. ### `wickedSaml.metadata()` Returns a function which can be used directly as the `metadata.xml` end point, when using express. ```javascript app.get('/auth-server/metadata.xml', wickedSaml.metadata()); ``` ### `wickedSaml.login(callback)` Create a request identifier and login URL for redirecting to the SAML IdP. **Example**: ```javascript // Assume /auth-server/:apiId?client_id=3498wzio4e57648576348756345 app.get('/auth-server/:apiId', function (req, res, next) { req.session.apiId = req.params.apiId; req.session.clientId = req.query.client_id; wickedSaml.login(function (err, loginInfo) { if (err) return next(err); req.session.requestId = loginInfo.requestId; res.redirect(loginInfo.loginUrl); }); }); ``` Note that there is a bunch of validity checking and security measures missing in the above code. **Callback signature**': `function(err, loginInfo)`, whereas `loginInfo`: ```javascript loginInfo = { loginUrl: 'https://...../idp1', requestId: '7hf5irutzerwiutzhw384765h8w47658w4f' } ``` Use the `loginUrl` to redirect to the IdP and store the `requestId` in your session for checking when you get called back in `/assert`. ### `wickedSaml.assert(req, requestId, callback)` Use this function to decrypt a SAML assertion. Call this from the `/assert` end point you specified in your configuration (`spOptions.assert_endpoint`): ```javascript app.post('/auth-server/assert', function (req, res, next) { const requestId = req.session.requestId; wickedSaml.assert(req, requestId, function (err, userInfo, samlResponse) { if (err) return next(err); // More elaborate error handling if needed // userInfo will contain "authenticated_userid" property (most of the time) // If you need other things, use getAttributeValue() to retrieve from // the samlResponse: userInfo.authenticated_userid = wickedSaml.getAttributeValue(samlResponse, 'our_company_id'); // Fill in the other values for use with the Kong Adapter, stored // in session (see login()) userInfo.api_id = req.session.apiId; userInfo.client_id = req.session.clientId; // In case you need to do some authorization step (this is only authentication), // this is the place to do that, e.g. check for licenses for the authenticated // user, which could be passed on as OAuth2 scopes: userInfo.scope = ['some_scope', 'other_scope']; wicked.getRedirectUriWithAccessToken(userInfo, function (err, redirect) { if (err) return next(err); // Yay, done! Redirect back to web app res.redirect(redirect.redirect_uri); }) }); }); ``` **Callback signature**: `function(err, userInfo, samlResponse)` The `userInfo` looks as follows: ```javascript userInfo = { authenticated_userid: "some-id-we-found" } ``` `wickedSaml.assert` will try to extract these two values (as needed for `getRedirectUriWithAccessToken`), but cannot guarantee it will work out. Additionally, if you have multiple fields in your SAML response which ends with `id`, any one will be picked. So it's **recommended** that you explicitly set those values manually using the `samlResponse` and the `getAttributeValue()` function (see below). ### `wickedSaml.getAttributeNames(samlResponse)` Lists all attribute names of the `user` tag of the given SAML response (`samlRespose`). Takes the SAML response from `assert()` as an argument and returns a string array. **Note**: The attribute names will be converted to lower case. ### `wickedSaml.getAttributeValue(samlResponse, wantedAttribute)` Retrieve the value of an attribute in the `samlResponse`. The `wantedAttribute` parameter is not case-sensitive. If the attribute cannot be found, `null` is returned. ### `wickedSaml.getConfig()` Returns the configuration object the SAML SDK retrieved from the wicked API (e.g., the `auth-saml.json` settings from the `auth-servers` configuration of your API portal). ### `wickedSaml.getLogoutResponseUrl(inResponseTo, relayState, callback)` To be written.