Posted in : ADFS, NetScaler Av Simon Gottschlag Översätt med Google ⟶
7 years ago
We’ve been seeing an issue with AAA in front of ADFS where credentials entered at the service provider (Office 365 for example) doesn’t populate the username in the NetScaler login, which works with ADFS. This isn’t the biggest issue, but something that makes it annoying to use AAA instead of pure ADFS. We were able to do this just fine with the cookie NSC_NAME (or even query based) before when not using RfWebUI. Because RfWebUI is the latest and greatest as well as responsive, most want to use it.
I’ve been looking into how to solve this using RfWebUI and may not have found the best solution in the world, but it works reliably and is easy to implement. A big thanks to Sam Jacobs who helped me out with the javascript parts, I haven’t been working with it before so was crucial to tying the knot on the issue.
The first thing I had to figure out was how to extract the username that Office 365 sends to ADFS. We can see the username in the query to ADFS as follows:
https://<ADFS FQDN>/adfs/ls/?[...]&username=<URL Encoded Email>&[...]
Note: It is URL Encoded which means the @ will be presented as %40.
The thing is that when using AAA, a new redirect will be made directly inside NetScaler to the AAA vserver for authentication. I was thinking of either trying to add ”Set-Cookie: UserNameCookie=<email>” somewhere here but I was thinking that this may not work since rewrites doesn’t always work on internal redirects and I may have to add another redirect to an already long chain of redirects – which may cause issues for some browser. What I did find was a cookie named NSC_TASS that contains a long string of random letters, numbers and symbols. After trying some things I was able do decode it by first converting it from URL Encoded format and then from Base64. When doing this, I was able to see the original ADFS URL containing the query with the username. To do this, I had to run the following to get the email/username in the correct format for the NetScaler login:
HTTP.REQ.COOKIE.VALUE("NSC_TASS").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE.B64DECODE.TYPECAST_HTTP_URL_T.QUERY.VALUE("username").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE
In other words we do the following: Grab the value of NSC_TASS and decode the URL Encoding. After that, decode it using Base64. Then typecast it to a HTTP URL and grab the value of the query username, and decode the URL Encoding (converting %40 to @ in my case).
Now to the part where I had to get some help to actually insert the username into the form. The solutions works fine, but if you have a better way of doing it please share! We had to put a small loop and wait to make sure the input field is created before the username can be inserted. The result looks like this:
<script type="text/javascript" language="javascript"> window.onload = function() { var username = "<Username / Email>"; if (CTXS.getCookie("NSC_TASS") != null && document.referrer.length >= 1 && username.length >= 1) { var timer; var loopCount = 0; var waitForElement = function () { if (document.getElementById("login").value != null) { document.getElementById("login").value = username; document.getElementById("passwd").focus(); clearInterval(timer); } else { loopCount++; if (loopCount > 100) clearInterval(timer); } } timer = setInterval(waitForElement, 100); } }; </script>
We’re waiting for the window to load, but for some reason that doesn’t mean that the input field ”login” exists (yet) and that’s where the setInterval-loop comes in. Without the loop, I did see it work most of the times on computers but rarely on phones. To make sure that this only happens when being redirected, we’ll be verifying that the cookie NSC_TASS exists and that the referrer length is greater or equal 1. After that we verifies that the element ”login” is created and inserts the username / email and changes focus to the password input.
Now it’s just a matter of using a rewirte to insert this:
add rewrite action RWA-RES-NSGW_INSERT_USERNAME replace_all "HTTP.RES.BODY(96190).SET_TEXT_MODE(IGNORECASE)" "\"\\r\\n\"+\n\" <script type=\\\"text/javascript\\\" language=\\\"javascript\\\">\\r\\n\"+\n\" window.onload = function() {\\r\\n\"+\n\" var username = \\\"\" + HTTP.REQ.COOKIE.VALUE(\"NSC_TASS\").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE.B64DECODE.TYPECAST_HTTP_URL_T.QUERY.VALUE(\"username\").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE + \"\\\";\\r\\n\"+\n\" if (CTXS.getCookie(\\\"NSC_TASS\\\") != null && document.referrer.length >= 1 && username.length >= 1) {\\r\\n\"+\n\" var timer;\\r\\n\"+\n\" var loopCount = 0;\\r\\n\"+\n\" var waitForElement = function () {\\r\\n\"+\n\" if (document.getElementById(\\\"login\\\").value != null) {\\r\\n\"+\n\" document.getElementById(\\\"login\\\").value = username;\\r\\n\"+\n\" document.getElementById(\\\"passwd\\\").focus();\\r\\n\"+\n\" clearInterval(timer);\\r\\n\"+\n\" } else {\\r\\n\"+\n\" loopCount++;\\r\\n\"+\n\" if (loopCount > 100)\\r\\n\"+\n\" clearInterval(timer);\\r\\n\"+\n\" }\\r\\n\"+\n\" }\\r\\n\"+\n\" timer = setInterval(waitForElement, 100);\\r\\n\"+\n\" }\\r\\n\"+\n\" };\\r\\n\"+\n\" </script>\\r\\n\"+\n\"\\r\\n\"+\n\"</body>\\r\\n\"+\n\"</html>\\r\\n\"" -pattern "</body>\n</html>" add rewrite policy RWP-RES-NSGW_INSERT_USERNAME "HTTP.REQ.COOKIE.VALUE(\"NSC_TASS\").LENGTH.GE(1) && HTTP.REQ.URL.PATH_AND_QUERY.SET_TEXT_MODE(IGNORECASE).EQ(\"/logon/LogonPoint/index.html\")" RWA-RES-NSGW_INSERT_USERNAME bind vpn vserver <NSGW vServer> -policy RWP-RES-NSGW_INSERT_USERNAME -priority 110 -gotoPriorityExpression NEXT -type RESPONSE
If you are using the GUI, the rewrite part looks like this:
"\r\n"+ " <script type=\"text/javascript\" language=\"javascript\">\r\n"+ " window.onload = function() {\r\n"+ " var username = \"" + HTTP.REQ.COOKIE.VALUE("NSC_TASS").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE.B64DECODE.TYPECAST_HTTP_URL_T.QUERY.VALUE("username").SET_TEXT_MODE(URLENCODED).DECODE_USING_TEXT_MODE + "\";\r\n"+ " if (CTXS.getCookie(\"NSC_TASS\") != null && document.referrer.length >= 1 && username.length >= 1) {\r\n"+ " var timer;\r\n"+ " var loopCount = 0;\r\n"+ " var waitForElement = function () {\r\n"+ " if (document.getElementById(\"login\").value != null) {\r\n"+ " document.getElementById(\"login\").value = username;\r\n"+ " document.getElementById(\"passwd\").focus();\r\n"+ " clearInterval(timer);\r\n"+ " } else {\r\n"+ " loopCount++;\r\n"+ " if (loopCount > 100)\r\n"+ " clearInterval(timer);\r\n"+ " }\r\n"+ " }\r\n"+ " timer = setInterval(waitForElement, 100);\r\n"+ " }\r\n"+ " };\r\n"+ " </script>\r\n"+ "\r\n"+ "</body>\r\n"+ "</html>\r\n"
I hope this can help some people out there making their end users happier! If you find a way of doing this easier, please share!
Tags : AAA, ADFS, NetScaler, NetScaler Gateway, RfWebUI
Personlig rådgivning
Vi erbjuder personlig rådgivning med författaren för 1400 SEK per timme. Anmäl ditt intresse i här så återkommer vi så snart vi kan.
Comments
Barry Schiffer says
This is truly awesome work guys! I have had this question many times and was quite sure there should be a way to do it but didn’t know how.
Jochen Hoffmann says
Great article, indeed. Nice work!
Roel Schreibers says
It is work like this that gets me enthusiastic!! Thank you, this is very helpful.
Simon Gottschlag says
Thank you all for the kind comments! It's fun solving puzzles with NetScaler!
Add comment