Tech With Htunn
  • Blog Content
  • ๐Ÿค–Artificial Intelligence
    • ๐Ÿง Building an Intelligent Agent with Local LLMs and Azure OpenAI
    • ๐Ÿ“ŠRevolutionizing IoT Monitoring: My Personal Journey with LLM-Powered Observability
  • ๐Ÿ“˜Core Concepts
    • ๐Ÿ”„Understanding DevSecOps
    • โฌ…๏ธShifting Left in DevSecOps
    • ๐Ÿ“ฆUnderstanding Containerization
    • โš™๏ธWhat is Site Reliability Engineering?
    • โฑ๏ธUnderstanding Toil in SRE
    • ๐Ÿ”What is Identity and Access Management?
    • ๐Ÿ“ŠMicrosoft Graph API: An Overview
    • ๐Ÿ”„Understanding Identity Brokers
  • ๐Ÿ”ŽSecurity Testing
    • ๐Ÿ”SAST vs DAST: Understanding the Differences
    • ๐ŸงฉSoftware Composition Analysis (SCA)
    • ๐Ÿ“‹Software Bill of Materials (SBOM)
    • ๐ŸงชDependency Scanning in DevSecOps
    • ๐ŸณContainer Scanning in DevSecOps
  • ๐Ÿ”„CI/CD Pipeline
    • ๐Ÿ”My Journey with Continuous Integration in DevOps
    • ๐Ÿš€My Journey with Continuous Delivery and Deployment in DevOps
  • ๐ŸงฎFundamentals
    • ๐Ÿ’พWhat is Data Engineering?
    • ๐Ÿ”„Understanding DataOps
    • ๐Ÿ‘ทThe Role of a Cloud Architect
    • ๐Ÿ›๏ธCloud Native Architecture
    • ๐Ÿ’ปCloud Native Applications
  • ๐Ÿ›๏ธArchitecture & Patterns
    • ๐Ÿ…Medallion Architecture in Data Engineering
    • ๐Ÿ”„ETL vs ELT Pipeline: Understanding the Differences
  • ๐Ÿ”’Authentication & Authorization
    • ๐Ÿ”‘OAuth 2.0 vs OIDC: Key Differences
    • ๐Ÿ”Understanding PKCE in OAuth 2.0
    • ๐Ÿ”„Service Provider vs Identity Provider Initiated SAML Flows
  • ๐Ÿ“‹Provisioning Standards
    • ๐Ÿ“ŠSCIM in Identity and Access Management
    • ๐Ÿ“กUnderstanding SCIM Streaming
  • ๐Ÿ—๏ธDesign Patterns
    • โšกEvent-Driven Architecture
    • ๐Ÿ”’Web Application Firewalls
  • ๐Ÿ“ŠReliability Metrics
    • ๐Ÿ’ฐError Budgets in SRE
    • ๐Ÿ“SLA vs SLO vs SLI: Understanding the Differences
    • โฑ๏ธMean Time to Recovery (MTTR)
Powered by GitBook
On this page
  • The "Click" Moment: Understanding the Two SAML Journeys
  • SP-Initiated Flow: The User Starts at Your Application
  • IdP-Initiated Flow: The User Starts at the Identity Provider
  • My Real-World Implementation with Keycloak and Node.js
  • 1. Setting Up Keycloak for Both Flows
  • 2. My Node.js Implementation for SP-Initiated Flow
  • 3. Adding Support for IdP-Initiated Flow
  • The Key Differences I've Discovered Between the Flows
  • 1. Request Initiation and User Experience
  • 2. Technical Implementation Differences
  • 3. Security Considerations
  • My Decision Framework for Choosing Between Flows
  • Conclusion: The Flow Matters More Than You Think
  1. Authentication & Authorization

Service Provider vs Identity Provider Initiated SAML Flows

PreviousUnderstanding PKCE in OAuth 2.0NextSCIM in Identity and Access Management

Last updated 20 hours ago

I still remember the confusion I felt when I first encountered SAML authentication flows. "SP-initiated? IdP-initiated? Why are there different flows at all?" After implementing SAML in dozens of enterprise applications, I've developed a deep understanding of these flows and their important differences. Let me share what I've learned through my journey with SAML, Keycloak, and Node.js.

The "Click" Moment: Understanding the Two SAML Journeys

My lightbulb moment came when I visualized SAML flows as two different journeys to the same destination:

  • SP-initiated flow: The user starts their journey at your application and gets redirected to the identity provider for authentication

  • IdP-initiated flow: The user starts at the identity provider (like a company portal) and jumps directly to your application

This simple mental model completely changed how I approach SAML implementations. Let me walk you through both flows based on my real-world experience.

SP-Initiated Flow: The User Starts at Your Application

In my experience, SP-initiated flows are the most common scenario. Here's how I explain it:

Imagine you're visiting a museum (your application). When you try to enter a restricted exhibition, the guard (your app) asks for your membership card. Instead of verifying it themselves, they send you to the membership office (identity provider) where your identity is confirmed. You then return to the exhibition with proof of your membership, and the guard lets you in.

Here's the actual technical flow I implement:

What I love about SP-initiated flow is that it provides seamless authentication when users are already interacting with your application. The redirection to the IdP happens only when needed, making it feel like a natural part of the login process.

IdP-Initiated Flow: The User Starts at the Identity Provider

The IdP-initiated flow confused me at first, until I realized its value in enterprise environments. Here's how I visualize it:

Imagine you work at a large company with an internal portal (the IdP). From this portal, you can click on icons for various corporate applications. When you click on one, you're immediately taken to that application, already authenticated.

Here's the technical flow I've implemented in production systems:

The key difference I've learned is that in IdP-initiated flows, the SAML response is "unsolicited" - there's no corresponding AuthnRequest, which introduces some special handling requirements.

My Real-World Implementation with Keycloak and Node.js

Let me share how I've implemented both flows using Keycloak as the IdP and Node.js as my Service Provider.

1. Setting Up Keycloak for Both Flows

First, I configured Keycloak to support both flows:

  1. I created a dedicated realm for my application

  2. I added a SAML client with these settings:

Client ID: my-nodejs-app
Client Protocol: saml
Valid Redirect URIs: https://myapp.com/saml/acs
Master SAML Processing URL: https://myapp.com/saml/acs
Force POST Binding: ON
Front Channel Logout: ON
Name ID Format: email
  1. For IdP-initiated flow support, I configured:

IDP Initiated SSO URL Name: my-app
IDP Initiated SSO Relay State: /dashboard
  1. I created a few test users and assigned them roles

2. My Node.js Implementation for SP-Initiated Flow

Here's how I implemented SP-initiated flow in Node.js (the more common scenario):

// app.js - SP-Initiated SAML with Keycloak
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const { Strategy } = require('passport-saml');
const fs = require('fs');
const path = require('path');

const app = express();

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET || 'my-secret',
  resave: false,
  saveUninitialized: true,
  cookie: { 
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));

// Load your private key for signing requests
const privateKey = fs.readFileSync(path.join(__dirname, 'certs', 'private.key'), 'utf8');

// Load the IdP's public certificate for verification
const idpCert = fs.readFileSync(path.join(__dirname, 'certs', 'idp.crt'), 'utf8');

// SAML Strategy configuration
const samlStrategy = new Strategy({
  // Service Provider settings
  callbackUrl: 'https://myapp.com/saml/acs',
  entryPoint: 'https://keycloak.mycompany.com/auth/realms/my-realm/protocol/saml',
  issuer: 'my-nodejs-app',
  signatureAlgorithm: 'sha256',
  privateKey: privateKey,
  
  // Identity Provider settings
  cert: idpCert,
  
  // Additional settings I've found important
  disableRequestedAuthnContext: true,
  acceptedClockSkewMs: 5000,
  
  // For better debugging during development
  validateInResponseTo: true,
  requestIdExpirationPeriodMs: 3600000 // 1 hour
}, (profile, done) => {
  // In production I do additional verification here
  // and often look up the user in a database
  return done(null, {
    id: profile.nameID,
    email: profile.email || profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
    name: profile.displayName,
    // Include any additional attributes from the SAML assertion
    attributes: profile
  });
});

passport.use(samlStrategy);

// Serialize/deserialize user to session
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

app.use(passport.initialize());
app.use(passport.session());

// Middleware to check if user is authenticated
const ensureAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
};

// Route that initiates the SP-initiated SAML flow
app.get('/login', passport.authenticate('saml', {
  failureRedirect: '/login-failed',
  failureFlash: true
}));

// SAML Assertion Consumer Service endpoint
app.post('/saml/acs', 
  passport.authenticate('saml', { 
    failureRedirect: '/login-failed',
    failureFlash: true 
  }),
  (req, res) => {
    // After successful authentication
    res.redirect(req.session.returnTo || '/dashboard');
  }
);

// Protected route that requires authentication
app.get('/dashboard', ensureAuthenticated, (req, res) => {
  res.send(`
    <h1>Welcome, ${req.user.name}!</h1>
    <p>Your email: ${req.user.email}</p>
    <pre>${JSON.stringify(req.user, null, 2)}</pre>
    <a href="/logout">Logout</a>
  `);
});

// Metadata endpoint - I found this essential for easy Keycloak configuration
app.get('/metadata', (req, res) => {
  res.type('application/xml');
  res.send(samlStrategy.generateServiceProviderMetadata(
    fs.readFileSync(path.join(__dirname, 'certs', 'public.pem'), 'utf8')
  ));
});

app.get('/login-failed', (req, res) => {
  res.status(401).send('Authentication failed');
});

app.get('/logout', (req, res) => {
  req.logout();
  res.redirect('/');
});

app.get('/', (req, res) => {
  res.send(`
    <h1>SAML SP Example</h1>
    <p>This is a public page.</p>
    <a href="/dashboard">Access Dashboard</a>
  `);
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

3. Adding Support for IdP-Initiated Flow

This is where things got interesting for me. To support IdP-initiated flows, I had to modify my SAML strategy configuration and ACS endpoint handler:

// Modified settings for the SAML strategy to support IdP-initiated flow
const samlStrategy = new Strategy({
  // Include all previous settings
  
  // This is the key setting for IdP-initiated flows
  validateInResponseTo: true,
  // For IdP-initiated flows, we need to disable strict validation
  // since there's no AuthnRequest to validate against
  allowUnsolicitedResponses: true
}, (profile, done) => {
  // Same callback as before
});

// Modified ACS endpoint to handle both SP and IdP initiated flows
app.post('/saml/acs', 
  passport.authenticate('saml', { 
    failureRedirect: '/login-failed',
    failureFlash: true 
  }),
  (req, res) => {
    // Check if this was an IdP-initiated flow (no returnTo in session)
    // or an SP-initiated flow (has returnTo)
    if (req.session.returnTo) {
      // SP-initiated flow - return to the original requested URL
      const returnTo = req.session.returnTo;
      delete req.session.returnTo;
      res.redirect(returnTo);
    } else {
      // IdP-initiated flow - go to the default landing page
      res.redirect('/dashboard');
    }
  }
);

I also had to ensure my session configuration properly handled IdP-initiated flows, which sometimes arrive without any prior session context.

The Key Differences I've Discovered Between the Flows

After implementing both flows across many projects, here are the key differences I've discovered:

1. Request Initiation and User Experience

SP-Initiated:

  • Starts when users attempt to access your application

  • Users see your application first, then get redirected to login

  • After authentication, users return to the specific resource they tried to access

  • Better for standalone applications with their own entry point

IdP-Initiated:

  • Starts from the identity provider's portal or dashboard

  • Users never see an unauthenticated version of your application

  • After authentication, users typically land on a default page in your application

  • Better for enterprise environments with a central application portal

2. Technical Implementation Differences

SP-Initiated:

  • Your application generates an AuthnRequest

  • SAML Response references the AuthnRequest ID

  • Prevents certain replay attacks through InResponseTo validation

  • Generally more secure due to the request-response pattern

IdP-Initiated:

  • No AuthnRequest is generated (unsolicited response)

  • SAML Response has no InResponseTo attribute

  • Requires special configuration to accept "unsolicited" responses

  • May need additional security measures like strong RelayState validation

  • Often requires special handling in your application code

3. Security Considerations

This was a big learning for me: IdP-initiated flows have some additional security considerations:

SP-Initiated:

  • The AuthnRequest ID serves as an anti-replay mechanism

  • The flow is harder to tamper with, as both sides have matching requests and responses

IdP-Initiated:

  • No AuthnRequest means one less verification point

  • Potentially more vulnerable to replay attacks

  • May require additional security measures like shorter assertion validity periods

  • Need to be careful with RelayState handling to prevent open redirects

My Decision Framework for Choosing Between Flows

After implementing both flows many times, here's my personal decision framework:

Use SP-Initiated When:

  • Your users primarily access your application directly

  • You need maximum security

  • You need to direct users to specific pages after authentication

  • You're building public-facing applications

Use IdP-Initiated When:

  • Your application is part of an enterprise suite

  • Users primarily access your app through a company portal

  • User experience priority is seamless access from the IdP

  • You need to support "deep linking" from an IdP dashboard

Support Both When:

  • You have enterprise customers who expect IdP-initiated flows

  • You also have users who bookmark your application directly

  • You want to provide maximum flexibility

Conclusion: The Flow Matters More Than You Think

When I first started implementing SAML, I didn't appreciate how much the choice of flow would impact both the user experience and security model. Now I understand that choosing the right flow isn't just a technical decisionโ€”it's a fundamental aspect of how users will interact with your application.

In most of my recent projects, I've chosen to support both flows with a preference for SP-initiated when possible. This gives my applications the security benefits of the request-response pattern while still accommodating enterprise customers who require IdP-initiated access from their company portals.

Whether you're just starting with SAML or looking to optimize your existing implementation, I hope my experiences help you navigate the sometimes confusing world of SAML authentication flows!

๐Ÿ”’
๐Ÿ”„