Getting started with SSO (with code samples)
SSO (Single Sign-On) is a mechanism that allows a user to authenticate and access multiple web applications or services with just one set of login credentials. It provides a seamless login experience, eliminating the need for users to remember multiple usernames and passwords for different applications.
There are several benefits of using Single Sign-On (SSO) for authentication and access control in web applications. Here are some of the key benefits:
- Improved user experience: SSO provides a seamless login experience for users by allowing them to access multiple applications or services with just one set of credentials. This eliminates the need for users to remember and enter multiple usernames and passwords, which can be time-consuming and frustrating.
- Increased security: SSO can help improve security by reducing the risk of weak or reused passwords, which are common causes of data breaches. With SSO, users only need to remember one strong password, which reduces the likelihood of password-related security incidents.
- Reduced administrative overhead: SSO can help reduce administrative overhead by centralizing authentication and access control. This eliminates the need to manage user accounts and credentials for multiple applications, which can be time-consuming and error-prone.
- Simplified compliance: SSO can help simplify compliance with regulatory requirements by providing a centralized point of control for authentication and access control. This can help organizations meet compliance requirements more easily and efficiently.
- Cost savings: SSO can help reduce costs by eliminating the need for separate authentication and access control systems for each application. This can help reduce the complexity and cost of IT infrastructure and support, and can also help reduce the risk of downtime and service disruptions.
Overall, SSO can provide significant benefits for both users and organizations by improving security, simplifying administration, and enhancing the user experience.
How to implement SSO
To implement SSO in your web app login page, you can follow these steps:
- Choose an SSO protocol: There are several SSO protocols available, such as SAML, OAuth, and OpenID Connect. You need to choose the one that fits your requirements and integrate it with your application.
- Implement SSO in your web application: You need to configure your web application to accept the SSO protocol you have chosen. This involves setting up the authentication flow, handling the user identity and session information, and integrating with your identity provider.
- Set up an identity provider: An identity provider (IDP) is a service that authenticates users and provides their identity information to your web application. You can set up your own IDP or use a third-party IDP service such as Okta, Auth0, or OneLogin.
- Configure your web application with the IDP: You need to configure your web application to communicate with the IDP, so it can authenticate users and receive their identity information. This involves setting up the SSO connection and configuring the necessary security settings.
- Test and verify: Once you have implemented SSO, you need to test and verify that it works as expected. You can test the SSO flow by logging in to your web application using the SSO credentials and verifying that you can access the application without needing to enter any additional login credentials.
Implementing SSO can be complex, and it may require significant development effort. However, it provides a lot of benefits, such as improved user experience, reduced password fatigue, and enhanced security.
What programming languages can I use?
There are many back-end programming languages and frameworks that can be used to implement SSO in your web application. Some popular options include:
Java
Java is a popular language for building enterprise-level web applications, and there are several SSO frameworks available for it, such as Spring Security and Apache Shiro.
Sample with Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SsoFilter ssoFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(ssoFilter, BasicAuthenticationFilter.class)
.csrf().disable();
}
}
Python
Python is a versatile language that is used for a wide range of applications, including web development. There are several Python-based SSO frameworks, such as Flask-Security and Django-SSO.
Sample with Flask-Security:
from flask import Flask, redirect, url_for
from flask_security import Security, login_required, current_user
from flask_security.core import Identity
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret'
app.config['SECURITY_PASSWORD_SALT'] = 'super-salt'
# Define identity callback function
def get_identity(payload):
return Identity(payload['id'])
# Initialize security
security = Security(app, identity_loader=get_identity)
# Protected view
@app.route('/protected')
@login_required
def protected():
return 'Welcome, {}!'.format(current_user.name)
# Redirect to login page
@app.route('/')
def index():
return redirect(url_for('security.login'))
PHP
PHP is a popular language for building dynamic web applications, and there are several SSO frameworks available for it, such as SimpleSAMLphp and phpCAS.
Sample with SimpleSAMLphp:
require_once('vendor/autoload.php');
$as = new \SimpleSAML\Auth\Simple('default-sp');
// Redirect to SSO provider
if (!$as->isAuthenticated()) {
$as->login();
}
// Get user attributes
$attributes = $as->getAttributes();
// Print user name
echo 'Welcome, ' . $attributes['cn'][0];
Node.js
Node.js is a popular runtime environment for building scalable and efficient web applications. There are several Node.js-based SSO frameworks, such as Passport and Node-SSO.
Sample with Passport:
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const SAMLStrategy = require('passport-saml').Strategy;
const app = express();
app.use(session({ secret: 'super-secret' }));
app.use(passport.initialize());
app.use(passport.session());
// Configure SAML strategy
passport.use(new SAMLStrategy({
callbackUrl: 'https://localhost:3000/login/callback',
entryPoint: 'https://sso.example.com/idp',
issuer: 'https://localhost:3000',
cert: '-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----'
}));
// Serialize and deserialize user
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
// Protected view
app.get('/protected', passport.authenticate('saml'), function(req, res) {
res.send('Welcome, ' + req.user.nameID);
});
// Redirect to SSO provider
app.get('/login', passport.authenticate('saml'));
// Callback endpoint
app.post('/login/callback',
passport.authenticate('saml', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/protected');
}
);
app.listen(3000);
Ruby
Ruby is a popular language for building web applications, and there are several Ruby-based SSO frameworks, such as Devise and OmniAuth.
Sample with Devise:
# Configure Devise with SAML
Devise.setup do |config|
config.saml_configure do |saml|
saml.idp_sso_target_url = 'https://sso.example.com/idp/sso'
saml.idp_cert_fingerprint = 'AB:CD:EF...'
saml.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
saml.assertion_consumer_service_url = 'https://
.NET
.NET is a popular framework for building Windows-based web applications, and there are several SSO frameworks available for it, such as IdentityServer and Kentor.AuthServices.
Sample with IdentityServer4 framework:
using IdentityServer4;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Security.Claims;
public class AccountController : Controller
{
private readonly IAuthenticationSchemeProvider _schemeProvider;
public AccountController(IAuthenticationSchemeProvider schemeProvider)
{
_schemeProvider = schemeProvider;
}
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult Login(string username, string password, string returnUrl = null)
{
if (username == "user" && password == "password")
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "user")
};
var claimsIdentity = new ClaimsIdentity(claims, IdentityServerConstants.DefaultCookieAuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true
};
HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties).Wait();
return LocalRedirect(returnUrl);
}
ModelState.AddModelError("", "Invalid username or password");
return View();
}
[HttpGet]
public IActionResult Logout()
{
HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme).Wait();
return RedirectToAction("Index", "Home");
}
[HttpGet]
[Authorize]
public IActionResult Profile()
{
return View();
}
[HttpGet]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
var properties = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(ExternalLoginCallback), new { returnUrl })
};
return Challenge(properties, provider);
}
[HttpGet]
[AllowAnonymous]
public IActionResult ExternalLoginCallback(string returnUrl = null)
{
var result = HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme).Result;
var externalId = result.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, externalId),
new Claim(ClaimTypes.Name, "user")
};
var claimsIdentity = new ClaimsIdentity(claims, IdentityServerConstants.DefaultCookieAuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true
};
HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties).Wait();
return LocalRedirect(returnUrl);
}
}
public static class IdentityServerConfig
{
public static IEnumerable<IdentityResource> IdentityResources => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
public static IEnumerable<Client> Clients => new List<Client>
{
new Client
{
ClientId = "webapp",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5001/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }
}
};
public static IEnumerable<ApiScope> ApiScopes => new List<ApiScope>
{
new ApiScope("api1", "My API")
};
public static IEnumerable<ApiResource> ApiResources => new List<ApiResource>
{
new ApiResource("api1", "My API")
{
Scopes = { "api1" }
}
};
public static IEnumerable<Client> ApiClients => new List<Client>
{
new Client
{
ClientId = "apiClient",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "api1" }
}
};
public static void AddIdentityServer(this IServiceCollection services)
{
services.AddIdentityServer()
.AddInMemoryIdentityResources(IdentityResources)
.AddInMemoryApiResources(ApiResources)
.AddInMemoryClients(Clients)
.AddDeveloperSigningCredential();
}
public static void AddAuthenticationAndAuthorization(this IServiceCollection services)
{
services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddCookie(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddOpenIdConnect("oidc", "SSO Provider", options =>
{
options.Authority = "https://sso.example.com";
options.ClientId = "webapp";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
var schemes = context.HttpContext.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
var handler = schemes.GetHandlerAsync(context.HttpContext, "oidc").Result;
if (handler is RemoteAuthenticationHandler<OpenIdConnectOptions> remoteHandler)
{
remoteHandler.Options.ProtocolValidator.RequireNonce = false;
}
return Task.FromResult(0);
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAuthenticatedUser", policy => policy.RequireAuthenticatedUser());
});
}
public static void AddCorsPolicy(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAnyOrigin",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
}
The choice of programming language and framework depends on several factors, such as the complexity of your application, the skills of your development team, and your performance and scalability requirements.
Conclusion
Single Sign-On (SSO) is a mechanism that allows users to access multiple applications or services with just one set of login credentials, providing a seamless and secure login experience. SSO can offer many benefits for users and organizations, including improved security, reduced administrative overhead, simplified compliance, and cost savings. There are several SSO protocols and frameworks available for different programming languages, which can help simplify the development and implementation process. By implementing SSO in your web application, you can provide your users with a better login experience while improving security and reducing administrative complexity.