I'm writing a Node based web service that is going expecting a third party to send a SAML request that's deflated and base64 encoded. After flailing around for the better part of the day I fear I'm not sure I even know what to ask (I apologize if this is a dupe).
The docs say:
The SAMLRequest is first DEFLATE-compressed, then Base 64 encoded, then URL encoded. It must be decoded and parsed
The Webstorm Console log has this query string:
GET /login?SAMLRequest=fZJRa9swEMff9ymE3m1LipM4InbJ0mwrNCM0bqF9Gap8cQW2lOnksH37ObFbWgZ9lLj76X%2F30%2FLqT9uQE3g0zuaUx4wSsNpVxtY5vS%2B%2FRRm9Kr4sUbWNOMpVF17sHfzuAANZIYIPfd%2FaWexa8HvwJ6Ph%2Fu42py8hHFEmCV%2BImM%2BymMWCswRBd96Ev1GrrKrBJ2eueuXokUPJdc83VoVLqDPqA4lPmJwwxpLG1cZScnOd018HJeaLSsNkqhfAqql6nsyyVFQiEweWqnlfhrv%2BKXOCnB5Ug3C%2BwQ5uLAZlQ04F42nE5hHPSj6VKZc8jfmcP1Gy8y447Zqvxg6L6byVTqFBaVULKIOW%2B9X2VoqYyeehCOWPstxFq36yg9LhAjmZCvzPviOn352rGyAbG8AfvUEg%2B3E3ZDvshpKHVy3irKUXZVEOIj5PcBzj0mLwJi9z%2BveEzwFvRmgxLr%2B%2BxI21axN4i5zUqJIyjTaP23SzftyK9Wwm%2FnO8TN6HKMbjx79U%2FAM%3D
Output of
console.log(req.query.SAMLRequest)
shows the URL decoding is automatically done:
fZJRa9swEMff9ymE3m1LipM4InbJ0mwrNCM0bqF9Gap8cQW2lOnksH37ObFbWgZ9lLj76X/30/LqT9uQE3g0zuaUx4wSsNpVxtY5vS+/RRm9Kr4sUbWNOMpVF17sHfzuAANZIYIPfd/aWexa8HvwJ6Ph/u42py8hHFEmCV+ImM+ymMWCswRBd96Ev1GrrKrBJ2eueuXokUPJdc83VoVLqDPqA4lPmJwwxpLG1cZScnOd018HJeaLSsNkqhfAqql6nsyyVFQiEweWqnlfhrv+KXOCnB5Ug3C+wQ5uLAZlQ04F42nE5hHPSj6VKZc8jfmcP1Gy8y447Zqvxg6L6byVTqFBaVULKIOW+9X2VoqYyeehCOWPstxFq36yg9LhAjmZCvzPviOn352rGyAbG8AfvUEg+3E3ZDvshpKHVy3irKUXZVEOIj5PcBzj0mLwJi9z+veEzwFvRmgxLr++xI21axN4i5zUqJIyjTaP23SzftyK9Wwm/nO8TN6HKMbjx79U/AM=
So I assume I must decode the base64 then INFLATE it. (I've tried to inflate and then decode but that still failed and this makes the most sense):
var zlibJS = require('zlibjs'); //I've tried Node's own zlib but that only appears to work with call backs
...
var b = new Buffer(samlReq, 'base64');
var decoded = b.toString();
var inflated = zlibJS.inflateSync(decoded); //https://github.com/imaya/zlib.js/blob/master/test/node-test.js
//I get an Unsupported compression method
...//relooking at the test it appears it wants a buffer so switch the above line for this
var inflated = zlibJS.inflateSync(b); // same 500 error
Looking through the github source this error is thrown when a switch falls through to the default when looking for a compression method however, I don't see where I'm expected to set that. I've tried to log like crazy(stripped out for readability) and put each step into base64 decoder (http://www.motobit.com/util/base64-decoder-encoder.asp), url decoder (http://meyerweb.com/eric/tools/dencoder/), and a string uncompressor (http://i-tools.org/gzip) with inconclusive results (the uncompressor tool fails to uncompress its own compression so...)
Thanks for any advice, tips and or tools that might help
I had seen another SO answer mention pako and wikipedia mentions it in the ZLib article so I gave it a whirl and it worked second try:
var pako = require('pako');
...
var b = new Buffer(samlReq, 'base64');
var inflated = pako.inflateRaw(b, {to:'string'});
Since you are using server-side JavaScript with Node.js, encoding and decoding of SAML Request and Response payloads can easily be done with no external dependencies by making use of Node's built-in zlib
and Buffer
support. There's no need for an external system like pako
(even though it's pretty cool).
I've done just that with my saml-encode-decoder-js
NPM package. It's used like this for decoding:
var saml = require('saml');
saml.decodeSamlRedirect(xml, function(err, xml) {
if (!err) {
console.log("Redirect decoded XML", xml);
}
}
saml.decodeSamlPost(xml, function(err, xml) {
if (!err) {
console.log("POST decoded XML", xml);
}
}
That NPM library is just a shortcut for the use of the built-in functions. For example, this is how saml-encoder-decoder-js
decodes a SAML redirect binding payload:
var zlib = require('zlib');
function decodeSamlRedirect(encoded, cb) {
if (encoded == null || encoded == "") {
return cb(new Error('Cannot decode null string'));
}
var deflated = new Buffer(decodeURIComponent(encoded), 'base64');
zlib.inflateRaw(deflated, function (err, inflated) {
if (!err) {
return cb(null, inflated.toString('ascii'));
} else {
return cb(err);
}
});
}
The trick is the use of zlib.deflateRaw()
and zlib.inflateRaw()
for encoding and decoding. You can't use deflate()
and unzip()
.
If you are using SAML POST bindings, then really this is just a wrapper for using Node's Buffer
for Base64 encoding/decoding, like here:
function decodeSamlPost(encoded, cb) {
if (encoded == null || encoded == "") {
return cb(new Error('Cannot decode null string'));
}
return cb(null, new Buffer(encoded, 'base64').toString('ascii'));
}
From your question I derived that you're trying to make the the SAML Request that should be deflated, base64 encoded and afterwards URL encoded (last part only because it its used as part of an url)
I'm not sure if async can be used to solve your synchronous problem, but I would go with something similar to this:
var async = require('async');
var zlib = require('zlib');
var url = require('url');
var samlRequest = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">'
+ '...'
+ '</samlp:AuthnRequest>';
var buffer = new Buffer(samlRequest);
async.waterfall(
[
function deflate(callback) {
zlib.deflate(buffer, function deflated(err, resultBuffer) {
if (err) {
callback(err);
return;
}
callback(null, resultBuffer);
});
},
function encode(buffer, callback) {
callback(null, buffer.toString('base64'));
},
function urlEncode(request, callback) {
callback(null, encodeURIComponent(request));
}
], function handleResult(err, urlEncodedRequest) {
if (err) {
console.log(err);
return;
}
var baseUrl = 'http:xxx.salesforce.com/idp/endpoint/HttpRedirect?SAMLRequest=';
console.log(baseUrl + urlEncodedRequest);
}
);
The samlRequest
is of course just an example. I used three function which definitely is overkill, but I wanted to show you async in the process as well, and it nicely demonstrates all the steps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With