Change expired password during login

This solution document will give you an idea on how to configure the login flow to detect expired password and prompt the user for a password change.

This document will use the username, password and OTP scenario as an example, but can be replaced by any other authentication flow.

Prereqs

This will work with federated setups, but part of the authentication has to be made with "internal authenticators". You will need to find the proper internal authenticator that matches the SAML authenticator to be replaced.

Please make sure to replace all references to match your environment.

The templates files used in this document can be found on https://files.phenixid.se in the folder PhenixID MFA PAS/Integrations/Change expired password during login.

Authenticators

SAMLDataSave - start

This will be the point of start for this flow.

<p>{
		"name": "SAMLDataSave",
		"id": "start",
		"alias": "start",
		"configuration": {
			"nextAuthenticator": "internal-UidPWDOTP",
			"idpID": "idp"
		}
	}</p>

PostUidPasswordAndOTP - internal-UidPWDOTP

This is the authenticator that performs the username, password and OTP validation.

This is also the step where the password expiration is detected and stored in the session.

<p>{
		"id": "internal-UidPWDOTP",
		"alias": "internal-UidPWDOTP",
		"name": "PostUidPasswordAndOTP",
		"configuration": {
			"userValidationPipeID": "UserLookupAndAuthWithLDAP",
			"otpValidationPipeID": "ValidateSentOtp",
			"successURL": "/saml/authenticate/dispatch/"
		}
	}</p>

Dispatch - dispatch

The user will be dispatched to the password change if the password is expired.

The user will be dispatched to the "sso authenticator" if the password not is expired.

<p>{
		"name": "Dispatch",
		"id": "dispatch",
		"alias": "dispatch",
		"configuration": {
			"idpID": "idp",
			"mapping": [
				{
					"authenticator": "changepwd",
					"expression": "session.properties().getValueOrDefault('changepwd','').equals('true')"
				},
				{
					"authenticator": "sso",
					"expression": "!session.properties().getValueOrDefault('changepwd','').equals('true')"
				}
			]
		}
	}</p>

Registration - changepwd

This authenticator will perform the password change.

<p>{
		"alias": "changepwd",
		"id": "changepwd",
		"name": "Registration",
		"configuration": {
			"stages": [
				{
					"pipeid": "ChangePWDPipe",
					"template": "pwchange",
					"sessionValues": [
						"username",
						"customError",
						"dummy"
					],
					"translation": []
				},
				{
					"pipeid": "",
					"template": "pwchange-redir",
					"sessionValues": [
						"redirect_to_saml_uri"
					],
					"translation": []
				}
			]
		}
	}</p>

PostUidAndPasswordSAML - sso

This authenticator will create the SAML assertion and login the user to the SP.

<p>{
		"name": "PostUidAndPasswordSAML",
		"id": "sso",
		"alias": "sso",
		"configuration": {
			"pipeID": "PostUid_and_Password_SAML_SSO",
			"idpID": "idp"
		}
	}</p>

Pipes

UserLookupAndAuthWithLDAP

This pipe will perform username and password validation, generate and send OTP.

The session will be updated if the password is expired.

<p>{
		"id": "UserLookupAndAuthWithLDAP",
		"valves": [
			{
				"name": "LDAPSearchValve",
				"config": {
					"connection_ref": "8d7362da-15c4-4178-888d-086b337e6f33",
					"base_dn": "dc=company,dc=local",
					"scope": "SUB",
					"size_limit": "0",
					"filter_template": "(&amp;(objectclass=*)(samaccountname={{request.username}}))",
					"attributes": "commonName,uid,mail,mobile"
				}
			},
			{
				"name": "LDAPBindValve",
				"config": {
					"connection_ref": "8d7362da-15c4-4178-888d-086b337e6f33",
					"password_param_name": "password",
					"allowed_error_codes": "532,773"
				}
			},
			{
				"name": "SessionLoadValve",
				"config": {
					"id": "{{request.session_id}}",
					"exec_if_expr": "flow.firstItem().containsProperty('LDAP_BIND_ERROR_CODE')"
				}
			},
			{
				"name": "SessionPropertyReplaceValve",
				"config": {
					"name": "changepwd",
					"value": "true",
					"exec_if_expr": "flow.firstItem().containsProperty('LDAP_BIND_ERROR_CODE')"
				}
			},
			{
				"name": "SessionPropertyReplaceValve",
				"config": {
					"name": "oldpassword",
					"value": "{{request.password}}",
					"exec_if_expr": "flow.firstItem().containsProperty('LDAP_BIND_ERROR_CODE')"
				}
			},
			{
				"name": "SessionPropertyReplaceValve",
				"config": {
					"name": "username",
					"value": "{{request.username}}",
					"exec_if_expr": "flow.firstItem().containsProperty('LDAP_BIND_ERROR_CODE')"
				}
			},
			{
				"name": "SessionPersistValve",
				"config": {
					"exec_if_expr": "flow.firstItem().containsProperty('LDAP_BIND_ERROR_CODE')"
				}
			},
			{
				"name": "OTPGeneratorValve",
				"config": {
					"length": "6",
					"name": "generated_otp"
				}
			},
			{
				"name": "OTPBySMSValve",
				"enabled": "false",
				"config": {
					"userid_param_name": "username",
					"gw_username": "GatewayAccount",
					"gw_password": "GatewayPassword"
				}
			}
		]
	}</p>

ValidateSentOtp

This pipe will validate the OTP.

<p>{
		"id": "ValidateSentOtp",
		"valves": [
			{
				"name": "SessionLoadValve",
				"config": {
					"id": "{{request.session_id}}"
				}
			},
			{
				"name": "OTPValidationValve",
				"enabled": "false",
				"config": {
					"provided_otp_param_name": "{{request.otp}}",
					"generated_otp_param_name": "generated_otp"
				}
			}
		]
	}</p>

ChangePWDPipe

This pipe will do the password change.

<p>{
		"id": "ChangePWDPipe",
		"valves": [
			{
				"name": "SessionLoadValve",
				"config": {
					"id": "{{request.session_id}}"
				}
			},
			{
				"name": "LDAPSearchValve",
				"config": {
					"connection_ref": "8d7362da-15c4-4178-888d-086b337e6f33",
					"base_dn": "dc=company,dc=local",
					"scope": "SUB",
					"size_limit": "0",
					"filter_template": "(&amp;(objectclass=*)(samaccountname={{session.username}}))",
					"attributes": "commonName,uid,mail,mobile,pwdLastSet"
				}
			},
			{
				"name": "ADPasswordChangeValve",
				"config": {
					"connection_ref": "8d7362da-15c4-4178-888d-086b337e6f33",
					"value": "{{request.pwdnew}}",
					"unlock": "false",
					"current_password_param_name": "{{session.oldpassword}}"
				}
			},
			{
				"name": "SessionPropertyRemoveValve",
				"config": {
					"name": "changepwd"
				}
			},
			{
				"name": "SessionPropertyReplaceValve",
				"config": {
					"name": "redirect_to_saml_uri",
					"value": "/saml/authenticate/sso"
				}
			},
			{
				"name": "SessionPersistValve",
				"config": {}
			}
		]
	}</p>

PostUid_and_Password_SAML_SSO

This pipe will issue the SAML assertion and redirect the end user to the SP.

<p>{
		"id": "PostUid_and_Password_SAML_SSO",
		"valves": [
			{
				"name": "FlowFailValve",
				"config": {
					"message": "No current session",
					"skip_if_expr": "request.authenticatedrequest=='true'"
				}
			},
			{
				"name": "SessionLoadValve",
				"config": {
					"id": "{{request.session_id}}"
				}
			},
			{
				"name": "LDAPSearchValve",
				"config": {
					"connection_ref": "8d7362da-15c4-4178-888d-086b337e6f33",
					"base_dn": "dc=company,dc=local",
					"scope": "SUB",
					"size_limit": "0",
					"filter_template": "(sAMAccountName={{request.username}})",
					"attributes": "sAMAccountName"
				}
			},
			{
				"name": "AssertionProvider",
				"config": {
					"targetEntityID": "idp",
					"sourceID": "https://apache.phenixid.se/saml",
					"userNameAttribute": "sAMAccountName"
				}
			}
		]
	}
</p>