php - SAML IDP in Laravel - Logging out
Get the solution ↓↓↓I have a Laravel application where users can access a third party application via my application (acting as an Identity Provider).
The logging in was achieved by taking inspiration from: https://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-make-Response/
<?php
namespace App\Http\Controllers\Saml;
use App\Http\Controllers\Controller;
use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn;
class LoginController extends Controller
{
use PerformsSingleSignOn;
/**
* Initialise a new constructotr instance.
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('verified');
$this->middleware('investor.eligible');
$this->init();
}
/**
* Display the login form for accessing SAML
*
* @return void
*/
public function showLoginForm()
{
return view('user.custodian');
}
/**
* Attempt to log into a given Service Provider.
*
* @return void
*/
public function login()
{
$this->setDestination();
return $this->samlResponse();
}
/**
* Send a SAML message to the intended Service Provider with security assertions and other bearer assertions.
* In this case we're explicitly sending email and name id
*
* @link https://www.lightsaml.com/LightSAML-Core/Cookbook/How-to-make-Response/
*
* @return void
*/
public function samlResponse()
{
// Create a new SAML Response instance
$this->response = new \LightSaml\Model\Protocol\Response();
// Create a response object that we'll send later.
$this->response
->addAssertion($assertion = new \LightSaml\Model\Assertion\Assertion())
->setStatus(
new \LightSaml\Model\Protocol\Status(
new \LightSaml\Model\Protocol\StatusCode(
\LightSaml\SamlConstants::STATUS_SUCCESS
)
)
)
->setID(\LightSaml\Helper::generateID())
->setIssueInstant(new \DateTime())
->setDestination($this->destination)
->setIssuer(new \LightSaml\Model\Assertion\Issuer($this->issuer));
// Build out our Assertion instance
$assertion
->setId(\LightSaml\Helper::generateID())
->setIssueInstant(new \DateTime())
->setIssuer(new \LightSaml\Model\Assertion\Issuer($this->issuer))
->setSignature(new \LightSaml\Model\XmlDSig\SignatureWriter($this->certificate, $this->private_key))
->setSubject(
(new \LightSaml\Model\Assertion\Subject())
->setNameID(new \LightSaml\Model\Assertion\NameID(
auth()->user()->email,
\LightSaml\SamlConstants::NAME_ID_FORMAT_EMAIL
))
->addSubjectConfirmation(
(new \LightSaml\Model\Assertion\SubjectConfirmation())
->setMethod(\LightSaml\SamlConstants::CONFIRMATION_METHOD_BEARER)
->setSubjectConfirmationData(
(new \LightSaml\Model\Assertion\SubjectConfirmationData())
->setNotOnOrAfter(new \DateTime('+1 MINUTE'))
->setRecipient($this->destination)
)
)
)
->setConditions(
(new \LightSaml\Model\Assertion\Conditions())
->setNotBefore(new \DateTime())
->setNotOnOrAfter(new \DateTime('+1 MINUTE'))
->addItem(
new \LightSaml\Model\Assertion\AudienceRestriction([$this->destination])
)
)
->addItem(
(new \LightSaml\Model\Assertion\AttributeStatement())
->addAttribute(new \LightSaml\Model\Assertion\Attribute(
\LightSaml\ClaimTypes::EMAIL_ADDRESS,
auth()->user()->email,
))
->addAttribute(new \LightSaml\Model\Assertion\Attribute(
\LightSaml\ClaimTypes::NAME_ID,
auth()->user()->id,
))
)
->addItem(
(new \LightSaml\Model\Assertion\AuthnStatement())
->setAuthnInstant(new \DateTime('-10 MINUTE'))
->setSessionIndex(\LightSaml\Helper::generateID())
->setAuthnContext(
(new \LightSaml\Model\Assertion\AuthnContext())
->setAuthnContextClassRef(\LightSaml\SamlConstants::AUTHN_CONTEXT_WINDOWS)
)
);
return $this->sendSAMLResponse();
}
/**
* Send a SAML response to the Service Provider and display the end result to the user.
* If this is successful it should log the user into the SP.
*
* @param \LightSaml\Model\Protocol\Response $response
*
* @return void
*/
private function sendSAMLResponse()
{
$bindingFactory = new \LightSaml\Binding\BindingFactory();
$postBinding = $bindingFactory->create(\LightSaml\SamlConstants::BINDING_SAML2_HTTP_POST);
$messageContext = new \LightSaml\Context\Profile\MessageContext();
$messageContext->setMessage($this->response)->asResponse();
$httpResponse = $postBinding->send($messageContext);
return $httpResponse->getContent();
}
/**
* Set the destination.
*
* @return void
*/
private function setDestination()
{
$destination = config('samlidp.sp.aHR0cHM6Ly9zZXJ2aWNlcy11ay5zdW5nYXJkZHguY29tL1NpbmdsZVNpZ25Pbi9TZXJ2aWNlUHJvdmlkZXI=.destination');
$this->destination = $destination;
}
}
I run into a snag however when I try to perform a SAML Single Log Out from an SP.
The steps are as follows:
- SP sends a logout request
- IDP responds
So I have a logout controller similar to the above.
<?php
namespace App\Http\Controllers\Saml;
use App\Http\Controllers\Controller;
use App\Jobs\SamlSlo as JobsSamlSlo;
use CodeGreenCreative\SamlIdp\Traits\PerformsSingleSignOn;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use LightSaml\Helper;
use LightSaml\Model\Assertion\Issuer;
use LightSaml\Model\Assertion\NameID;
use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Protocol\LogoutRequest;
use LightSaml\SamlConstants;
use Log;
class LogoutController extends Controller
{
use PerformsSingleSignOn;
private $sp;
/**
* @param [type] $sp [description]
*/
public function __construct()
{
$this->sp = config('samlidp.sp.aHR0cHM6Ly9zZXJ2aWNlcy11ay5zdW5nYXJkZHguY29tL1NpbmdsZVNpZ25Pbi9TZXJ2aWNlUHJvdmlkZXI=');
$this->init();
}
public function index()
{
$this->setDestination();
$this->setDestination();
return redirect($this->request());
}
/**
* [request description]
* @return [type] [description]
*/
public function request()
{
$this->response = (new LogoutRequest)
->setIssuer(new Issuer($this->issuer))
->setNameID((new NameID(Helper::generateID(), SamlConstants::NAME_ID_FORMAT_TRANSIENT)))
->setID(Helper::generateID())
->setIssueInstant(new \DateTime)
->setDestination($this->destination);
return $this->send(SamlConstants::BINDING_SAML2_HTTP_REDIRECT);
}
private function setDestination()
{
$destination = $this->sp['logout'];
$parsed_url = parse_url($destination);
parse_str($parsed_url['query'] ?? '', $parsed_query_params);
$parsed_query_params['idp'] = config('app.url');
$this->destination = strtok($destination, '?') . '?' . http_build_query($parsed_query_params);
Log::info($this->destination);
}
}
The main issue is that in every example I've seen, the SPs have a dedicated logging out URL.
If I set the logout URL as a route within my IDP would this flow still work?
Share solution ↓
Additional Information:
Link To Answer People are also looking for solutions of the problem: port 80 in use by "unable to open process" with pid 4!
Didn't find the answer?
Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.
Similar questions
Find the answer in similar questions on our website.
Write quick answer
Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.