Google SSO using SAML

PHPMaker version: v2023.13After reading the documentation, I can’t seem to get Google SSO using SAML to work.I provided the following the administrator.

Entity ID: stgapp001v2023
ACS URL: https://stgapp001.mydomain.com/login/saml
Logout URL: https://stgapp001.mydomain.com/logout

and after the administrator done setup, they provide me with the following 2 files:

  1. cert file - mycertfile.pem
  2. Idp metadata file - GoogleIDPMeta.xml

Do I put this file in my website? I try to put this file in my website folder and configure the Advanced Setting with the following:

SAML Idp Metadata (xml) : https://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml
SAML SP Entity ID: stgapp001v2023
SAML SP X.509 certificate file: https://stgapp001.mydomain.com/sso/mycertfile.pem
SAML SP private key file: <empty>

Do I setup this correctly? Do I need the private key as I was only provided the .pem file?and if I login as hardcoded admin, it shows the following error when I click Logout.D:\myprojectfolder\vendor\hkvstore\lightsaml\src\LightSaml\Model\Metadata\EntityDescriptor.php(64): DOMDocument::loadXML(): Argument #1 ($source) must not be empty

yinsw wrote:

DOMDocument::loadXML(): Argument #1 ($source) must not be empty

The error said it cannot read the meta data, your IdP metadata URL https://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml is probably wrong (does not return XML).Note that “Idp” is identity provider, it should be Google’s server, not your site (which is “SP”, i.e. service provider). If you see Tutorial - Single Sign-On with SAML, you’ll see that the URL is on “microsoftonline.com”, not on your site. If you can download the metadata (XML) of the Google IdP, you can put it on your site though, then make sure the URL (to the metadata) returns the metadata in XML. You can test it in your browser.Also, the certificate file should not be a URL, you should specify a physical path or a relative path (relative to the project folder). If you specify the certificate, you need to provide the private key also. If you have not created any certificate and uploaded to the IdP yourself, you should not specify certificate (which is your SP’s certificate, not the IdP’s).Additional points you may check:

  1. If the script cannot get the meta data by HTTPS, then you can use HTTP, e.g. http://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml,
  2. In your Google admin account, make sure you have set “User Access” to “ON for everyone” , “Name ID format” to “Email”, “Name ID” to “Primary email”, and at least map “Primary email” to app attribute “email”.

Thanks for your reply. So as you mentioned, my website is the SP (service provider) and Google site is the Idp (Identity Provider).I was provided with a .xml file from their admin so I place it in my website which is accessible by URL. I’ve checked and the file is a valid xml file format. To verify it, I place it under my project folder /sso/GoogleIDPMetadata.xml and I can access it by using https://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml.I was also provided with a certificate file in .pem format. What should I do with this file? Since in “Advanced Settings → SAML SP x.509 certificate file” is referring to SP which is service provider, then what use is the cert file from my Idp?

arbei wrote:

If the > script > (> not > your browser) cannot get the meta data by HTTPS, then you can use > HTTP > (i.e. > http://> ), e.g. > http://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml
If you have not created any certificate and uploaded to the IdP yourself, you should > not > specify certificate (which is your SP’s certificate, not the IdP’s).

You don’t need to specify certificate for Idp since it should be already provided in the metadata XML.To recap,

  1. Set “SAML IdP metadata (XML)” as http://stgapp001.mydomain.com/sso/GoogleIDPMetadata.xml,
  2. Set the “SAML SP entity ID” as your Entity ID, i.e. “stgapp001v2023”,
  3. It shouldn’t be necessary to specify “SAML SP X.509 certificate file” and “SAML SP private key file” for Google SAML.

It is that simple. Works fine for me.

Thanks.I’ve got a little progress. After clicked Login with SAML, it will now redirect my to the google accounts login page. However once I login, I encountered the errror:app_not_configured_for user
Service is not configured for this user.I suspect it may be due to the attribute mapping which I did not set. Is it that I need to request them to add the following app attribute at the Google SSO Admin?givenname
surname
emailaddress
name
Unique User IdentifierIs all of it required or I only need to add attribute “Unique User Identifier”?

arbei wrote:

In your Google admin account, make sure you have set “User Access” to “ON for everyone” , “Name ID format” to “Email”, “Name ID” to “Primary email”, and at least map “Primary email” to app attribute “email”.

yinsw wrote:

app_not_configured_for user
Service is not configured for this user.

I doubt if you have set up your Google admin account properly, the error message said “Service is not configured for this user”, it does not look like related to attribite mapping.

I’ve added app attribute “email” which map to the primary email. I’ve also noticed the Entity ID I configured is wrong so I corrected that.I turned on debug mode under the AUTH_CONFIG and saw this error below which I’ve fixed by changing the word “saml” to “Saml”.[2023-08-18T10:01:48.351034+08:00] log.ERROR: Unsuccessful SAML response: urn:oasis:names:tc:SAML:2.0:status:Requester Invalid request, ACS Url in request https://stgapp001.mydomain.com/login/Saml doesn’t match configured ACS Url https://stgapp001.mydomain.com/login/saml. urn:oasis:names:tc:SAML:2.0:status:RequestDenied After all is fixed, I can successfully authenticate now but I was returned to the login page instead of automatically login in. Any more steps that I missed?Below is the log file:

[2023-08-18T10:48:19.232160+08:00] log.INFO: PHPMaker2023\MyProject\Saml2::authenticate() [] []
[2023-08-18T10:48:19.287511+08:00] log.DEBUG: PHPMaker2023\MyProject\Saml2::authenticateFinish(), callback url: ["https://stgapp001.mydomain.com/login/Saml"] []

https://stgapp001.mydomain.com/login/Saml is my ACS URL.

If your project’s username field is not the email field, the user (logged in SAML by email) cannot continue to login the site, you should use User_CustomValidate server event to change the user from email to actual user name of the site.

Yeah, I do know this. I already have the user created where the UserID is the email address of the SSO User ID that I logged in with.

If the “UserID” you said is the project’s username field, then it should work. Make sure the user has permission to at least one page, if you use User Level Security.

I have a field called “LASTLOGIN” in my USERS table where I update it on User_LoggedIn() event. I checked this field was updated, which means it’s already logged in right? But somehow it can’t go to my default page which is my custom file. I’ve already given full permission to all the modules.

That probably means the user (with user name “xxx@gmail.com”) is not found in your user table. You better enable Debug and check the SQLs in the log file.

Only these in the log file after i authenticated successfully and redirect back:

[2023-08-21T14:42:27.193910+08:00] log.DEBUG: Initialize PHPMaker2023\MyProject\Saml2, config:  {"enabled":true,"adapter":"PHPMaker2023\\MyProject\\Saml2","idpMetadata":"https://stgapp01.mydomain.com/sso/GoogleIDPMetadata.xml","entityId":"stgapp001v2023","certificate":"","privateKey":"","color":"success","callback":"https://stgapp01.mydomain.com/login/Saml","debug_mode":true,"debug_file":""} []
[2023-08-21T14:42:27.197370+08:00] log.INFO: PHPMaker2023\MyProject\Saml2::authenticate() [] []
[2023-08-21T14:42:27.207325+08:00] log.DEBUG: Signing disabled [] []
[2023-08-21T14:42:27.214106+08:00] log.DEBUG: PHPMaker2023\MyProject\Saml2::authenticateBegin(), redirecting user to: ["https://accounts.google.com/o/saml2/idp?idpid=XXX&SAMLRequest=XXXXXX"] []
[2023-08-21T14:43:54.954148+08:00] log.DEBUG: Initialize PHPMaker2023\MyProject\Saml2, config:  {"enabled":true,"adapter":"PHPMaker2023\\MyProject\\Saml2","idpMetadata":"https://stgapp01.mydomain.com/sso/GoogleIDPMetadata.xml","entityId":"stgapp001v2023","certificate":"","privateKey":"","color":"success","callback":"https://stgapp01.mydomain.com/login/Saml","debug_mode":true,"debug_file":""} []
[2023-08-21T14:43:54.956649+08:00] log.INFO: PHPMaker2023\MyProject\Saml2::authenticate() [] []
[2023-08-21T14:43:54.988641+08:00] log.DEBUG: PHPMaker2023\MyProject\Saml2::authenticateFinish(), callback url: ["https://stgapp01.mydomain.com/login/Saml"] []

I’ve also checked the xml response and found the following:

	<saml2:Subject>
		<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">xxxx@gmail.com</saml2:NameID>
		<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
			<saml2:SubjectConfirmationData InResponseTo="_9ba1189ce5b4fdfdb2d9c00eb62baddadddd2cc716" NotOnOrAfter="2023-08-24T02:38:38.284Z" Recipient="https://stgapp01.mydomain.com/login/Saml"/>
		</saml2:SubjectConfirmation>

	<saml2:AttributeStatement>
		<saml2:Attribute Name="email">
			<saml2:AttributeValue
				xmlns:xs="http://www.w3.org/2001/XMLSchema"
				xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">xxxx@gmail.com
			</saml2:AttributeValue>
		</saml2:Attribute>
	</saml2:AttributeStatement>

The log means SAML succeeded and was redirected to the login page to continue and then no errors, that’s I suspected the user was not found. If you have PHP debugger, you better set a breakpoint after login with provider and trace why the user was not found. Be reminded again that the user name must be same as that in the user table.arbei wrote:

In your Google admin account, make sure you have set … “Name ID format” to “Email”, “Name ID” to “Primary email”, and at least map “primary email” to app attribute “email”.

If you set above properly, the current user after Google SSO using SAML should be “xxx@gmail.com” (you can verify by User_CustomValidate server event), you need to have an user with this email address as user name in your user table of the project or the user cannot be found.

I’ve added a debug in my User_CustomValidate() and it returns the correct User ID, which is the email that I login.I’ve checked my USERS table and the USERID field is the email address which I login with.I’ve also added a debug in my function User_LoggedIn($usr). For this function, the variable $usr returns empty value but the function CurrentUserID() returns my UserID which is the email that I logged in with.

yinsw wrote:

I’ve added a debug in my User_CustomValidate() and it returns the correct User ID, which is the email that I login.

This is correct, meaning the user email address is verified and passed to the event.


the function CurrentUserID() returns my UserID which is the email that I logged in with.

This is correct, meaning the user is found.


I’ve also added a debug in my function User_LoggedIn($usr). For this function, the variable $usr returns empty value

This is incorrect. The User_CustomValidate is fired first, then User_LoggedIn. So the $usr is lost after User_CustomValidate, make sure your User_CustomValidate event does not clear the $usr. (Make sure you use PHP >= 7.4.)You better set a breakpoint in your PHP debugger at the line: (in models/Login.php)$provider = trim(Get(“provider”) ?? Route(“provider”) ?? “”); // Get providerand then trace the login process and find out the problem.

$provider = trim(Get(“provider”) ?? Route(“provider”) ?? “”); // Get provider

This will returns the value “Saml”

} elseif (!EmptyValue($provider)) { // OAuth provider

  $provider = ucfirst(strtolower($provider)); // e.g. Google, Facebook
   $validate = $Security->validateUser($this->Username->CurrentValue, $this->Password->CurrentValue, false, $provider); // Authenticate by provider
   $validPwd = $validate;
   if ($validate) {
       $this->Username->setFormValue($UserProfile->get("email") ?? $UserProfile->get("emailaddress"));
       if (Config("DEBUG") && !$Security->isLoggedIn()) {
           $validPwd = false;
           $this->setFailureMessage(str_replace("%u", $this->Username->CurrentValue ?? "", $Language->phrase("UserNotFound"))); // Show debug message
       }
   } else {
       $this->setFailureMessage(str_replace("%p", $provider, $Language->phrase("LoginFailed")));
   }

} else { // Normal login

>

I debug and it will go into "if ($validate)" condition. I've checked the variable $UserProfile->get("email") will returns empty. the SESSION_STATUS also is empty.I confirmed that in Google Admin I already add an app attribute called "email".

yinsw wrote:

I confirmed that in Google Admin I already add an app attribute called “email”.

arbei wrote:

In your Google admin account, make sure you have set … > “Name ID format” to “Email”> , > “Name ID” to “Primary email”> , and at least map “Primary email” to app attribute “email”.

Are you sure you have set above in your Google admin account? They are 3 settings:

  1. Set “Name ID format” to “Email”,
  2. Set “Name ID” to “Primary email”,
  3. Map “Primary email” to app attribute “email”.

Do not skip the first two.

You should also check $UserProfile->Profile in your debugger and see what it contains.

Are you sure you have set above in your Google admin account? They are 3 settings: >

  1. Set “Name ID format” to “Email”,
  1. Set “Name ID” to “Primary email”,
  2. Map “primary email” to app attribute “email”.

Yup, confirmed already done all the 3 steps. The admin sent me screenshot to confirm this. It’s something like below:Name ID
Defines the naming formats supported by the identity provider.Name ID format
EMAIL_______Name ID
Basic Information > Primary Email__________________________________and in the SAML attribute mappingGoogle Directory attributes
Basic Information > Primary email__________________________________App attributes
email_______

Then it should work.arbei wrote:

You should also check > $UserProfile->Profile > in your debugger and see what it contains.

Note also that the arguments of User_CustomValidate server event is &$usr and &$pwd, make sure you have not removed the “&”.