diff --git a/lemonldap-ng-portal/t/30-Auth-and-issuer-SAML-Redirect-MultipleSP.t b/lemonldap-ng-portal/t/30-Auth-and-issuer-SAML-Redirect-MultipleSP.t
new file mode 100644
index 000000000..444f9a109
--- /dev/null
+++ b/lemonldap-ng-portal/t/30-Auth-and-issuer-SAML-Redirect-MultipleSP.t
@@ -0,0 +1,712 @@
+use lib 'inc';
+use Test::More;
+use strict;
+use IO::String;
+use LWP::UserAgent;
+use LWP::Protocol::PSGI;
+use MIME::Base64;
+
+BEGIN {
+ require 't/test-lib.pm';
+ require 't/saml-lib.pm';
+}
+
+my $maintests = 27;
+my $debug = 'error';
+my ( $issuer, $sp, $sp2, $res );
+my %handlerOR = ( issuer => [], sp => [], sp2 => [] );
+
+# Redefine LWP methods for tests
+LWP::Protocol::PSGI->register(
+ sub {
+ my $req = Plack::Request->new(@_);
+ fail('POST should not launch SOAP requests');
+ count(1);
+ return [ 500, [], [] ];
+ }
+);
+
+SKIP: {
+ eval "use Lasso";
+ if ($@) {
+ skip 'Lasso not found', $maintests;
+ }
+
+ # Initialization
+ ok( $issuer = issuer(), 'Issuer portal' );
+ $handlerOR{issuer} = \@Lemonldap::NG::Handler::Main::_onReload;
+ switch ('sp');
+ &Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
+
+ ok( $sp = sp(), 'SP portal' );
+ $handlerOR{sp} = \@Lemonldap::NG::Handler::Main::_onReload;
+
+ ok( $sp2 = sp2(), 'SP2 portal' );
+ $handlerOR{sp2} = \@Lemonldap::NG::Handler::Main::_onReload;
+
+ # Simple SP access
+ my $res;
+ ok(
+ $res = $sp->_get(
+ '/',
+ accept => 'text/html',
+ query => 'url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29tLw=='
+ ),
+ 'Unauth SP request'
+ );
+ my ( $host, $url, $query );
+ ok(
+ expectCookie( $res, 'lemonldapidp' ) eq
+ 'http://auth.idp.com/saml/metadata',
+ 'IDP cookie defined'
+ )
+ or explain(
+ $res->[1],
+'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp.com; path=/'
+ );
+ ( $url, $query ) = expectRedirection( $res,
+ qr#^http://auth.idp.com(/saml/singleSignOn)\?(SAMLRequest=.+)# );
+
+ # Push SAML request to IdP
+ switch ('issuer');
+ ok(
+ $res = $issuer->_get(
+ $url,
+ query => $query,
+ accept => 'text/html',
+ ),
+ 'Launch SAML request to IdP'
+ );
+ expectOK($res);
+ my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
+
+ # Try to authenticate to IdP
+ my $body = $res->[2]->[0];
+ $body =~ s/^.*?
//s;
+ $body =~ s#.*$##s;
+ my %fields =
+ ( $body =~ /_post(
+ $url,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => $pdata,
+ length => length($query),
+ ),
+ 'Post authentication'
+ );
+ expectOK($res);
+ my $idpId = expectCookie($res);
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
+ 'SAMLResponse', 'RelayState' );
+
+ # Post SAML response to SP
+ switch ('sp');
+ ok(
+ $res = $sp->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ cookie => 'lemonldapidp=http://auth.idp.com/saml/metadata',
+ ),
+ 'Post SAML response to SP'
+ );
+ my $spId = expectCookie($res);
+ expectRedirection( $res, 'http://test1.example.com/' );
+
+ ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
+ expectOK($res);
+ expectAuthenticatedAs( $res, 'fa@badwolf.org@idp' );
+
+ # Verify UTF-8
+ ok( $res = $sp->_get("/sessions/global/$spId"), 'Get UTF-8' );
+ expectOK($res);
+ ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
+ or print STDERR $@;
+ ok( $res->{cn} eq 'Frédéric Accents', 'UTF-8 values' )
+ or explain( $res, 'cn => Frédéric Accents' );
+
+ # Simple SP2 access
+
+ switch ('sp2');
+ ok(
+ $res = $sp2->_get(
+ '/',
+ accept => 'text/html',
+ query => 'url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29tLw=='
+ ),
+ 'Unauth SP2 request'
+ );
+
+ ok(
+ expectCookie( $res, 'lemonldapidp' ) eq
+ 'http://auth.idp.com/saml/metadata',
+ 'IDP cookie defined'
+ )
+ or explain(
+ $res->[1],
+'Set-Cookie => lemonldapidp=http://auth.idp.com/saml/metadata; domain=.sp2.com; path=/'
+ );
+ ( $url, $query ) = expectRedirection( $res,
+ qr#^http://auth.idp.com(/saml/singleSignOn)\?(SAMLRequest=.+)# );
+
+ # Push SAML request to IdP
+ switch ('issuer');
+ ok(
+ $res = $issuer->_get(
+ $url,
+ query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$idpId",
+ ),
+ 'Launch SAML request to IdP'
+ );
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.sp2.com', '/saml/proxySingleSignOnPost',
+ 'SAMLResponse', 'RelayState' );
+
+ # Post SAML response to SP2
+ switch ('sp2');
+ ok(
+ $res = $sp2->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ cookie => 'lemonldapidp=http://auth.idp.com/saml/metadata',
+ ),
+ 'Post SAML response to SP2'
+ );
+ my $sp2Id = expectCookie($res);
+ expectRedirection( $res, 'http://test1.example.com/' );
+
+ ok( $res = $sp2->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP2' );
+ expectOK($res);
+ expectAuthenticatedAs( $res, 'fa@badwolf.org@idp' );
+
+ # Logout initiated by SP
+ ok(
+ $res = $sp->_get(
+ '/',
+ query => 'logout',
+ cookie => "lemonldap=$spId",
+ accept => 'text/html'
+ ),
+ 'Query SP for logout'
+ );
+
+ ( $url, $query ) = expectRedirection( $res,
+ qr#^http://auth.idp.com(/saml/singleLogout)\?(SAMLRequest=.+)# );
+
+ # Push SAML logout request to IdP
+ switch ('issuer');
+ ok(
+ $res = $issuer->_get(
+ $url,
+ query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$idpId",
+ ),
+ 'Launch SAML logout request to IdP'
+ );
+
+ my $relaypage = $res;
+
+ ok( $res->[2]->[0] =~
+ m%