Over a million developers have joined DZone.

Integrating Node.js, Ruby and Spring with Okta's SAML Support

· Java Zone

Bitbucket is for the code that takes us to Mars, decodes the human genome, or drives your next car. What will your code do? Get started with Bitbucket today, it's free.

Security has always piqued my interest, ever since I first developed AppFuse and figured out how to make J2EE security work back in 2004. I hacked AppFuse to have Remember Me functionality, then moved onto Acegi/Spring Security. Spring Security had the features I needed, even if it did require almost 100 lines of XML to configure it. These days, it's much better and its JavaConfig - combined with Spring Boot - is pretty slick.

That was the first part of my security life. The second phase began the night I met Trish, and learned she sold security products. She knew of OWASP and their top 10 rules. It was Trish that inspired me to write my Java Web Application Security presentation. I really enjoyed writing that presentation, comparing Apache Shiro, Spring Security and Java EE's security frameworks. I followed up the first time I presented it with a number of blog posts and screencastsHmmmm, maybe I should update the presentation/screencasts to use Java configuration only (#NoXML) and submit it to a couple conferences this year? I digress.

I had to do a security-related spike over the last couple weeks. I was trying to get SAML authentication working with Okta and my client's Active Directory server. Luckily, someone setup the AD integration so all I had to do was try a few different languages/frameworks. I searched and found ThoughtWorks' okta-samples, which includes examples using Node.js and Sinatra (Ruby + JRuby). I also found a Spring SAML example that includes one of my favorite things in JavaLand: Java-based configuration.

I'm happy to report I was able to get all of these applications working with my client's Okta setup. This article will tell you how I did it. For each application, I created a new application on Okta using its "Template SAML 2.0 Application" and added myself in the application's "People" tab. Each section below contains the configuration I used for Okta. The instructions below assume you're similar to me, a developer that has Java 8, Node and Ruby installed, but none of the specific frameworks. As I write this, I have everything working on my Mac with Yosemite, but I wrote the instructions below using one of my old laptops, fresh after a Yosemite upgrade.

The first thing I did was checkout ThoughtWorks samples.

git clone https://github.com/ThoughtWorksInc/okta-samples.git


I started by getting the Node.js sample working. For Okta's configuration, I used:

Setting Value
Application label Okta Node.js Example
Force Authentication false
Post Back URL http://localhost:3000/login/callback
Name ID Format EmailAddress
Recipient http://localhost:3000/
Audience Restriction http://localhost:3000/
authnContextClassRef PasswordProtectedTransport
Response Signed
Assertion Signed
Request Compressed
Destination http://localhost:3000/login/callback
Attribute Statements email|${user.email},firstName|${user.firstName}

The Node.js sample uses express, as well as passport and passport-saml. The passport packages are used to handle the SAML authentication and connect is used to compress the requests from your local server.

The only thing I needed to do to make the Node.js app work was to paste the X509 cert string and target URL into its config.json from the Okta app. In Okta's Admin interface, I clicked on the "Sign On" tab and clicked its "View Setup Instructions" button. I copied the "Redirect Login URL" value and copied it into config.json's entryPoint value. I then downloaded the certificate and opened it in vi. I ran the following two commands to remove ^M and line endings (more details here).

Next, I copied everything between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- and pasted it into the cert value of config.json. I had to remove the comments from config.json for everything to work.

After making these changes, I was able to run "npm install" and "npm start" and successfully login at http://localhost:3000.


The Ruby sample uses Sinatraomniauth and omniauth-saml. To run the okta-ruby-sinatra application, I had to start by installing Bundler.

sudo gem install bundler

Then I installed all the required gems for this project using the following command.

bundle install

This resulted in the following error:

An error occurred while installing nokogiri (1.6.1), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.6.1'` succeeds before bundling

I tried Bundler's suggestion, but it failed:

Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby extconf.rb
mkmf.rb can't find header files for ruby at /System/Library/Frameworks/Ruby.

I then tried upgrading to Xcode 6.1.1. I received the same error and running "bundle update sinatra" and "sudo gem update --system" didn't help anything. I found an old Stack Overflow answer that suggested running "xcode-select --install" to install Xcode's Command Line Developer Tools. After doing so, I ran "sudo gcc" to accept to all Apple's licensing agreements. I ran "bundle install" again and this time it failed with the following error:

libxml2 is missing. please visit http://nokogiri.org/tutorials/installing_nokogiri.html for help with installing dependencies.
An error occurred while installing nokogiri (1.6.1), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.6.1'` succeeds before bundling.

I tried Bundler's suggested again: "sudo gem install nokogiri -v '1.6.1'". This didn't work, so I tried "bundle update" and it finally worked. I ran "bundle install" for the final time, followed by "ruby app.rb". WEBrick started and I created a "Okta Ruby Example" application on Okta with the following settings.

Setting Value
Application label Okta Ruby Example
Force Authentication false
Post Back URL http://localhost:4567/auth/saml/callback
Name ID Format EmailAddress
Recipient http://localhost:4567
Audience Restriction http://localhost:4567
authnContextClassRef PasswordProtectedTransport
Response Signed
Assertion Signed
Request Compressed
Destination http://localhost:4567/auth/saml/callback
Attribute Statements email|${user.email},firstName|${user.firstName}

To configure Sinatra with Otka's settings, I started by renaming the config.yml.sample file:

mv config.yml.sample config.yml

In Otka's Admin UI for the application, I clicked on the "Sign On" tab and clicked its "View Setup Instructions" button. I copied the "Redirect Login URL" value and copied it into config.yml's target_url value. I then downloaded the certificate and ran the the following command in the directory I downloaded it to.

openssl x509 -noout -fingerprint -in "okta.cert"

I copied the fingerprint into config.yml's fingerprint value and restarted the app. I opened http://localhost:4567 in my browser and was able to successfully login.


To start with JRuby, I first read the project's README. It mentioned issues with "nokogiri" and explains the project contains a patched release of nokogiri 1.6.0. Since I knew there was a later release, I modified Gemfile and removed the version and path information from the last line. I copied the config.yml from the Ruby project and ran the following commands to install Bundler, the project's dependencies and start the app.

jruby -S gem install bundler
jruby -S bundle install

Running the second command resulted in the following error:

Your jruby version is 1.7.18, but your Gemfile specified jruby 1.7.4

I modified Gemfile to specify "1.7.18" and tried again. This time it worked. I started the application using the following command:

jruby app.rb
NOTE: If you see the the following in your browser window, it means you forgot to copy config.yml from the Ruby project. 
undefined method `auth' for Sinatra::Application:Class

When I tried to login at http://localhost:4567, I saw an infinite redirect and the following error in my console.

W, [2015-01-08T08:53:22.514000 #56144] WARN -- : attack prevented by Rack::Protection::SessionHijacking
0:0:0:0:0:0:0:1 - - [08/Jan/2015 08:53:22] "GET / HTTP/1.1" 302 - 0.0190
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:53:22 MST] "GET / HTTP/1.1" 302 0

Stack Overflow indicated this is a problem caused by an old version of rack-protection. Running "jruby -S bundle update rack-protection" updated the project to use rack-protection 1.5.3 (was 1.5.1). After restarting and trying again, I received the following error:

I, [2015-01-08T08:59:32.679000 #56176] INFO -- omniauth: (saml) Callback phase initiated.
E, [2015-01-08T08:59:34.747000 #56176] ERROR -- omniauth: (saml) Authentication failure! invalid_ticket: Onelogin::Saml::ValidationError, Digest mismatch
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:34 -0700] "POST /auth/saml/callback HTTP/1.1" 302 9 2.0760
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:34 -0700] "GET /auth/failure?message=invalid_ticket&strategy=saml HTTP/1.1" 404 449 0.0080
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:34 MST] "GET /auth/failure?message=invalid_ticket&strategy=saml HTTP/1.1" 404 449
- -> /auth/failure?message=invalid_ticket&strategy=saml
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:34 -0700] "GET /__sinatra__/404.png HTTP/1.1" 200 18893 0.0200
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:32 MST] "POST /auth/saml/callback HTTP/1.1" 302 9
- -> /auth/saml/callback
0:0:0:0:0:0:0:1 - - [08/Jan/2015:08:59:34 MST] "GET /__sinatra__/404.png HTTP/1.1" 200 18893
http://localhost:4567/auth/failure?message=invalid_ticket&strategy=saml -> /__sinatra__/404.png

At this point, the only thing different from my working version and my old laptop was the version of Java. My old laptop had "build 1.8.0_05-b13", so I upgraded to the latest version of Java 8 (update 25). This didn't help, so I tried updating all bundles with "jruby -S bundle update". This failed too, so I figured I'd try to use the version of JRuby that was on my working laptop (version I installed Homebrew, ran "brew install jruby", removed the newer version from my path and downgraded the version in Gemfile. I had to re-install Bundler and the projects dependencies with the following commands.

jruby -S gem install bundler
jruby -S bundle install

Same error again. I reverted Gemfile.lock and ran the only bundle update command I'd run on my working laptop:

$ jruby -S bundle update sinatra

Unfortunately, this still didn't fix the issue. I copied the project from my working laptop and tried running that project. It failed, proving that it was an environment issue, not a project/code issue. I tried rebooting and when that didn't work, I gave up. It's pretty strange this didn't work on a fresh Yosemite install - it took me less than 10 minutes to get it working originally.


The Spring sample I got working with Okta was Vincenzo De Notaris' spring-boot-security-saml-sample. This project uses Spring Boot and Spring Security SAML. I created a "Okta Spring Example" application on Okta with the following settings.

Setting Value
Application label Okta Spring Example
Force Authentication false
Post Back URL http://localhost:8080/saml/SSO
Name ID Format EmailAddress
Recipient http://localhost:8080/saml/SSO
Audience Restriction com:vdenotaris:spring:sp
authnContextClassRef PasswordProtectedTransport
Response Signed
Assertion Signed
Request Uncompressed
Destination http://localhost:8080/saml/SSO
Attribute Statements email|${user.email},firstName|${user.firstName}

The biggest thing I learned while trying to get these values correct was that Request needs to be set to Uncompressed.

After cloning the GitHub project to my hard drive, I added a new SSO provider by adding a new bean to WebSecurityConfig.java. The URL I got from Okta's Admin UI: Sign On > View Setup Instructions > Public Link (near the bottom of the page).

@Bean(name = "idp-okta")
public ExtendedMetadataDelegate ssoOktaExtendedMetadataProvider()
      throws MetadataProviderException {
    @SuppressWarnings({ "deprecation"})
    HTTPMetadataProvider httpMetadataProvider
          = new HTTPMetadataProvider("https://client.okta.com/app/random-key-here/sso/saml/metadata", 5000);
    ExtendedMetadataDelegate extendedMetadataDelegate =
          new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
    return extendedMetadataDelegate;

For the SSL connection to work, I had to download the certificate and import it into the application's keystore. To do this in Chrome, I went to https://client.okta.com, clicked on the lock icon in the address bar, then dragged/dropped the certificate image to my desktop. This resulted in a *.okta.com.cer file on my desktop. I added it to the keystore using the following commands (thanks Stack Overflow).

keytool -importcert -file ~/Desktop/\*.okta.com.cer -keystore src/main/resources/saml/samlKeystore.jks

When prompted for the password, I entered "nalle123". This value is specified in WebSecurityConfig.java's keyManager bean. I then added this provider to the list of providers in the metadata bean.

public CachingMetadataManager metadata() throws MetadataProviderException {
        List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
    return new CachingMetadataManager(providers);

After making these changes, I started the application using "mvn spring-boot:run". I navigated to http://localhost:8080, chose Okta as my Idp and logged in successfully!


This article shows you how I got Node.js, Ruby and Spring applications working with Okta's SAML support. My experience with this when I first tried it: Node was super-easy, Ruby was a bit more difficult, JRuby was a cinch and Spring took several days. As you can tell from this article, Ruby/JRuby were the most difficult to make work on a clean machine.

All in all, working with Okta has been a pleasant experience so far. Hopefully this article helps make it a good experience for you as well.

Bitbucket is the Git solution for professional teams who code with a purpose, not just as a hobby. Get started today, it's free.


Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}