Intro

Single Sign-On (SSO) is a widely-used authentication method that makes applications more secure and reduces the attack surface for an organization. SAML (Security Assertion Markup Language) is a popular choice for implementing SSO and has been in use since the early 2000’s. Implementing SAML from scratch would be tricky for a beginner, but many great libraries have been written to streamline the work to set an integration up. In this blog post, I will show you how to add an SSO service provider integration to a Laravel application using the php-saml toolkit.

Service Providers vs Identity Providers

In this post, I will refer to the terms service provider (SP) and identity provider (IdP). These terms refer to the relationship between web applications in single sign-on.

An IdP is an application that verifies the identity of a user and checks what that user should have access to. This application could be Google, Active Directory or an IdP that you develop.

A service provider is an application that a user wants access to. This post will be focusing on building a service provider integration.

Prerequisites

Before we begin, ensure you have the following:

  • Laravel application.
  • Composer installed on your system.
  • An account with an IdP that supports SAML (like OneLogin, Okta, Google, etc.) or a test IdP like SAMLTest.id.

Installing the Toolkit

First, we need to include the php-saml library in our Laravel project. Run the following command in your project’s root directory:

composer require onelogin/php-saml

Configure SAML Settings

Make a new configuration file for your SAML settings at config/php-saml.php. This file will contain all settings for the toolkit, your SP and your IdP. Here’s a base to start from:

<?php

return [
  'strict' => true,

  // Enable debug mode (to print errors)
  'debug' => false,

  // Service Provider settings
  'sp' => [
    'entityId' => 'http://localhost:8000',
    'assertionConsumerService' => [
      'url' => 'http://localhost:8000/saml/acs',
      'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
    ],
    'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
    'x509cert' => '<cert.pem contents here>',
    'privateKey' => '<key.pem contents here>',
  ],

  // IdP settings
  'idp' => [
    'entityId' => '<provided by IdP>',
    'singleSignOnService' => [
      'url' => '<provided by IdP>',
      'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
    ],
    'x509cert' => '<provided by IdP>',
  ],

  'security' => [
    'authnRequestsSigned' => true,
    'allowRepeatAttributeName' => true,
  ],
];

Generating a Certificate Pair

An x.509 certificate is required for php-saml. It will be used to generate cryptographic signatures to include within SAML requests. This helps the IdP verify that it is negotiating a login with the correct application.

Run this openssl command in your terminal:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"

A pair of files will be generated: cert.pem and key.pem. Take the contents of cert.pem and save it in the SP x509cert setting. Do the same for key.pem but save it in the privateKey setting.

Note: While this tutorial has you add these keys to the configuration file for simplicity, it is highly recommended that you move these to environment variables or some manner of secret storage.

Configuring Your Identity Provider

Before the IdP settings can be filled out, additional setup is required in the admin panel of the IdP you are using. The method of getting your IdP set up will differ from provider to provider, but the process will generally follow the same steps:

  1. Create a new SAML 2.0 profile or application in the IdP
  2. Provide values from your application to your IdP (You can find them within config/php-saml.php):

    • Entity ID
    • Assertion Consumer Service URL
    • x.509 certificate
  3. Copy values from the IdP and save them in config/php-saml.php:

    • Entity ID
    • Single Sign-On Service URL
    • x.509 certificate

Set Up Routes

Define the necessary routes in routes/web.php to handle login redirects and SAML responses:

Route::get('/saml/login', [SAMLController::class, 'login']);
Route::post('/saml/acs', [SAMLController::class, 'acs'])
	->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);;

Create a SAML Controller

Generate a new controller named SAMLController:

php artisan make:controller SAMLController

In this controller, we’ll handle redirecting users to the IdP and handling SAML responses from the IdP:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use OneLogin\Saml2\Auth;

class SAMLController extends Controller
{
  public function login(Request $request)
  {
    $auth = new Auth(config('php-saml'));
    $redirectUrl = $auth->login(null, [], false, false, true);
    $request->session()->put('requestId', $auth->getLastRequestID());
    return redirect($redirectUrl);
  }

  public function acs(Request $request)
  {
    $auth = new Auth(config('php-saml'));
    $auth->processResponse($request->get('requestId'));

    if (count($auth->getErrors()) > 0 || !$auth->isAuthenticated()) {
      return 'An error occurred processing SAML response';
    }

    $user = User::query()->where('email', $auth->getNameId())->first();
    if (!$user) {
      return 'User not found.';
    }

    auth()->login($user);

    return redirect('/');
  }
}

In the acs method of the controller, you’ll need to authenticate users with the data provided by the IdP. This typically involves checking if the user exists in your database and creating a session for them. While this sample code assumes that every user in the IdP should already be in the database, if you want to automatically provision a user, this PHP method would be the place to do it.

Time to Test!

When you’re ready, open up a new browser tab and navigate to http://localhost:8000/saml/login. You should get automatically redirected to your IdP’s login page. After you login to the IdP, you should be redirected back to the SP /saml/acs endpoint. If validation is successful, a user is logged in to the Laravel application and the browser will be redirected to the home page.

Conclusion

I hope you’ve now successfully integrated SAML 2.0 into a Laravel application using the php-saml toolkit.

Remember to test your SSO integration thoroughly to ensure everything works as expected and that your application remains secure. Ensure that all SAML responses and requests are validated, handle errors appropriately, and keep your certificate credentials confidential.