It is already possible to show a checkbox in the WAYF that allows users to save this IdP-choice for future logins. If they select this, then future WAYFs in which their preferred IdP is present are skipped and the user is redirected to the preferred IdP without interaction.
We now want to able to specifiy this behaviour per SP. So when a users enabled the checkbox, this only applies when logging in to this specific SP. When the user logs in to a different SP, they still get the WAYF.
The following changes are required:
-
Add the following extra controls to parameters.yml:
- introduce feature-toggle
wayf.remember_choice_per_idp. All of the below should only be active if both wayf.remember_choice and wayf.remember_choice_per_idp are active.
- If
wayf.remember_choice is true and wayf.remember_choice_per_idp is false, we keep the current global memory feature.
- If
wayf.remember_choice is false, we disable all remember-my-choice features (both global and per-sp).
- If
wayf.remember_choice_per_idp is not defined, act as if it were set to false
- a parameter to control the life-time of the remembered IdP:
wayf.remember_choice_per_idp_lifetime. Typically this would be something like 3 months, but it would be nice to be able to set it to (e.g.) 60 seconds for easy debugging.
- a parameter to control how many SP-IdP pairs we can store in the cookie
wayf.remember_choice_per_idp_lifetime. This defaults to 16. For the logic what to do if we exceed this limit, see below.
-
support new coin:wayf_remember_choice metadata parameter for SPs. If this is not present in the metadata, it defaults to false.
-
if feature is enabled, read and set a cookie rememberedidps with content like this:
{
"https://sp_entityid_1": { "idp": "https://idp_entityid_1", "time": 1784534287 },
"https://sp_entityid_2": { "idp": "https://idp_entityid_2", "time": 1784512345 }
}
- Ideally, we would like this cookie to be user-readable (for power users and privacy nerds), but cookies are limited to 4kB so that probably won't work. So the safe way to handle this is to deflate and base64-encode the json, en put that in the cookie.
- To make sure the structure fits in the cookie, limit this to max 32 entries.
- If there are more, or a new one needs to be added, drop the oldest ones until there are 32 left.
- I've run a simulation with current production entities: for 32 entries, we never exceed 2800 bytes. For 64 entries, we sometimes hit 4500 bytes. For unencoded json, we cannot store more than 20 reliably within 4k bytes.
- It probably makes sense to parametrize this limit, so add a
wayf.remember_choice_per_idp_max parameter which defaults to 16 to be on the safe side.
- if the cookie content is in any way invalid (invalid base64 encoding, invalid json, etc), log a warning and simply ignore it and remove it from the response.
- The cookie needs to have a broader scope than just Engineblock, because we need to be able to read and reset it from Profile. Therefore give it a domain
surfconext.nl (configurable) rather than engine.surfconext.nl.
- The
Max-Age or Expires property of the cookie need to be set in accordance with the wayf.remember_choice_per_idp_lifetime setting.
- The cookie must have
Secure and HttpOnly properties (same as all other EB cookies). SameSite should probably be set to None because we support both Redirect and POST bindings for SAMLRequests.
-
If the cookie is present, and the user is logging in to an SP that is present in the cookie, then do the following:
- check that the SP's
time entry is less than wayf.remember_choice_per_idp_lifetime ago (this should never occur because of the cleanup check below, so it might not be nessecary to duplicate this check if we can be sure that the cleanup check runs before this logic)
- check that this SP has set
coin:wayf_remember_choice in the metadata
- check that this IdP is available for this SP
- if so, then skip the WAYF (like the existing
rememberchoice cookie does).
- if not, then remove this SP from the cookie-json and set an updated cookie, and show the regular WAYF
-
When reading/interpreting the cookie, take the following check actions. We need do do this for every AuthnRequest. It is not sufficient to do these checks only when a specific SP is hit, because then entries for removed SPs will linger forever. If changes are made, always update the cookie at the client.
- remove all expired entries (
time is older than wayf.remember_choice_per_idp_lifetime)
- remove any entries above the maximum number we support (see above). Drop the oldest ones until we are at the limit.
- remove entries for SPs that do not have
coin:wayf_remember_choice set in the metadata
- if no SPs are left in the cookie, remove the cookie altogether
-
If the users gets the regular WAYF and selects the "remember my choice" checkbox, then do the following if per-SP remembering is enabled (if global remembering is enabled, the behaviour doesn't change):
- Add the selected (SP,IdP) pair the to the cookie with the current time
- Run the checks as specified above (if it is not already run at this points in the flow anyway).
- Set the updated cookie in the response.
It is already possible to show a checkbox in the WAYF that allows users to save this IdP-choice for future logins. If they select this, then future WAYFs in which their preferred IdP is present are skipped and the user is redirected to the preferred IdP without interaction.
We now want to able to specifiy this behaviour per SP. So when a users enabled the checkbox, this only applies when logging in to this specific SP. When the user logs in to a different SP, they still get the WAYF.
The following changes are required:
Add the following extra controls to
parameters.yml:wayf.remember_choice_per_idp. All of the below should only be active if bothwayf.remember_choiceandwayf.remember_choice_per_idpare active.wayf.remember_choiceis true andwayf.remember_choice_per_idpis false, we keep the current global memory feature.wayf.remember_choiceis false, we disable all remember-my-choice features (both global and per-sp).wayf.remember_choice_per_idpis not defined, act as if it were set tofalsewayf.remember_choice_per_idp_lifetime. Typically this would be something like 3 months, but it would be nice to be able to set it to (e.g.) 60 seconds for easy debugging.wayf.remember_choice_per_idp_lifetime. This defaults to16. For the logic what to do if we exceed this limit, see below.support new
coin:wayf_remember_choicemetadata parameter for SPs. If this is not present in the metadata, it defaults tofalse.if feature is enabled, read and set a cookie
rememberedidpswith content like this:{ "https://sp_entityid_1": { "idp": "https://idp_entityid_1", "time": 1784534287 }, "https://sp_entityid_2": { "idp": "https://idp_entityid_2", "time": 1784512345 } }wayf.remember_choice_per_idp_maxparameter which defaults to 16 to be on the safe side.surfconext.nl(configurable) rather thanengine.surfconext.nl.Max-AgeorExpiresproperty of the cookie need to be set in accordance with thewayf.remember_choice_per_idp_lifetimesetting.SecureandHttpOnlyproperties (same as all other EB cookies).SameSiteshould probably be set toNonebecause we support both Redirect and POST bindings for SAMLRequests.If the cookie is present, and the user is logging in to an SP that is present in the cookie, then do the following:
timeentry is less thanwayf.remember_choice_per_idp_lifetimeago (this should never occur because of the cleanup check below, so it might not be nessecary to duplicate this check if we can be sure that the cleanup check runs before this logic)coin:wayf_remember_choicein the metadatarememberchoicecookie does).When reading/interpreting the cookie, take the following check actions. We need do do this for every AuthnRequest. It is not sufficient to do these checks only when a specific SP is hit, because then entries for removed SPs will linger forever. If changes are made, always update the cookie at the client.
timeis older thanwayf.remember_choice_per_idp_lifetime)coin:wayf_remember_choiceset in the metadataIf the users gets the regular WAYF and selects the "remember my choice" checkbox, then do the following if per-SP remembering is enabled (if global remembering is enabled, the behaviour doesn't change):