A user with passkey registrated will only be able to use passkey to login. A user with no passkey will fallback to password login interface. In the password login interface will the user input box be prepopulated with the username. The username can however be changed, but it will not be possible to login with a user that has passkey registrated as said and thereby no degrading can be done.

Configurations made to implement the scenario above come below.

It's passwordless flow that is used.

Configuration files involved:  
authn/mfa-authn-config.xml  
authn/authn.properties  
authn/webauthn.properties  
access-control.xml  
attribute-resolver.xml  
intercept/context-check-intercept-config.xml  
relying-party.conf  
  
authn/mfa-authn-config.xml:
```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="urn:mace:shibboleth:2.0:authn classpath:/schema/authn-config.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"

       default-init-method="initialize"
       default-destroy-method="destroy">

        <util:map id="shibboleth.authn.MFA.TransitionMap">
                <entry key="">
                        <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkPasswordOrWebAuthn" />
                </entry>
                <entry key="authn/WebAuthn">                                                                                     <bean parent="shibboleth.authn.MFA.Transition">                                                                  <property name="nextFlowStrategyMap">                                                                            <map>                                                                                                            <entry key="NoRegisteredWebAuthnCredentials" value="authn/Password" />                                                                                                                                    </map>                                                                                           </property>                                                                                      </bean>                                                                                          </entry>
        <!-- An implicit final rule will return whatever the final flow returns. -->
</util:map>
    <bean id="checkPasswordOrWebAuthn" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
        <constructor-arg>
            <value>
            <![CDATA[
                nextFlow = "authn/WebAuthn";

                // Go straight to second factor if we have to, or set up for an attribute lookup first.
                webauthnRegCtx = input.getSubcontext("net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext");
                if (webauthnRegCtx != null) {
                        if (!webauthnRegCtx.isWebAuthnAvailable()){
                            nextFlow = "authn/Password";
                        }
                }
                nextFlow;   // pass control to second factor or end with the first
            ]]>
            </value>
        </constructor-arg>
    </bean>

</beans>
```

authn/authn.properties (only parts that have been changed from default):
```
idp.authn.MFA.supportedPrincipals = \
    saml2/http://id.elegnamnden.se/loa/1.0/loa3, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken, \
    saml2/https://refeds.org/profile/mfa
    
idp.authn.flows = MFA
```

authn/webauthn.properties (only parts that have been changed from default):
```
idp.authn.webauthn.relyingPartyId = testidpshibboleth.irf.se # Only on test right now
idp.authn.webauthn.relyingPartyName = IRF Shibboleth
idp.authn.webauthn.supportedPrincipals = \                                                                       saml2/https://refeds.org/profile/mfa,\                                                                   saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password,\                                                  saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
idp.authn.webauthn.StorageService = JDBCStorageService 
idp.authn.webauthn.usernameless.enabled = false
# Enable this flow to act as a second factor.
idp.authn.webauthn.2fa.enabled = false
idp.authn.webauthn.metadata.enabled = true
idp.authn.webauthn.metadata.trustRootFile = /opt/shibboleth-idp/credentials/root-r3.crt
idp.authn.webauthn.metadata.crls = /opt/shibboleth-idp/credentials/root-r3.crl, /opt/shibboleth-idp/credentials/gsextendvalsha2g3r3.crl
idp.authn.webauthn.metadata.cacheFile = /opt/shibboleth-idp/metadata/fido_alliance.bin
idp.authn.webauthn.metadata.metadataBlobUrl = https://mds3.fidoalliance.org
idp.authn.webauthn.registration.attestationConveyancePreference = direct
idp.authn.webauthn.admin.registration.accessPolicy = AccessByCurrentUser

idp.authn.webauthn.admin.management.accessPolicy = AccessByAdmin
idp.authn.webauthn.admin.management.defaultAuthenticationMethods = saml2/https://refeds.org/profile/mfa
idp.authn.webauthn.passwordless.signalEventOnNoCredentials = true
idp.authn.webauthn.passwordless.noCredentialsEventId = NoRegisteredWebAuthnCredentials

#### Debugging
# Display debug information about the registration and authentication ceremony on their respective views?
idp.authn.webauthn.ui.debug = true
idp.webauthn.passwordless.explain = Please enter your username and press Continue
idp.webauthn.authn.authenticate = Test
```
access-control.xml (only part that is not default):
irfAuthorizedService it's an own attributet containing services that an user has access to.
```
<entry key="AccessByAdmin">
            <bean parent="shibboleth.PredicateAccessControl">
                <constructor-arg>
                    <bean parent="shibboleth.Conditions.SimpleAttribute">
                        <property name="attributeValueMap">
                            <map>
                                <entry key="irfAuthorizedService">
                                    <list>
                                          <value>shibAdmin</value>
                                    </list>
                               </entry>
                            </map>
                        </property>
                    </bean>
                </constructor-arg>
            </bean>
</entry>
```

attribute-resolver.xml (only relevant parts):
```
<!-- Check via ldap attribute irfAuthorizedSevice if user has passkey registered -->
<AttributeDefinition xsi:type="ScriptedAttribute" id="hasPasskey" xmlns="urn:mace:shibboleth:2.0:resolver">
        <InputDataConnector ref="myLDAP" attributeNames="irfAuthorizedService"/>
        <AttributeEncoder xsi:type="SAML2String" name="urn:oid:1.3.6.1.4.1.7592.1.1.13" friendlyName="irfAuthorizedService" encodeType="false" />
         <Script>
          <![CDATA[
                if ((irfAuthorizedService) && (irfAuthorizedService.getValues().contains("passkey")))
                         hasPasskey.getValues().add("yes");
                else
                         hasPasskey.getValues().add("no");
          ]]>
        </Script>
</AttributeDefinition>
<!-- Check directly in database if user has passkey registered -->
<AttributeDefinition xsi:type="ScriptedAttribute" id="hasPasskey2" xmlns="urn:mace:shibboleth:2.0:resolver">
        <InputDataConnector ref="myDatabase" attributeNames="haswebauthn"/>
        <Script>
                <![CDATA[
                        logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute.resolver.eppnbuilder");
                        if (typeof(haswebauthn)!="undefined") {
                                if (haswebauthn.getValues().contains("net.shibboleth.idp.plugin.authn.webauthn")){
                                        logger.info("values = test "+haswebauthn.getValues());
                                        hasPasskey2.getValues().add("yes");
                                }
                                else
                                        hasPasskey2.getValues().add("no");
                        }
                        else {
                                logger.info("has no passkey");
                                hasPasskey2.getValues().add("no");
                        }
                ]]>
        </Script>
</AttributeDefinition>
```
intercept/context-check-intercept-config.xml:
```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"               xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize"
default-destroy-method="destroy">
<!--
Returns true if a user with passkey uses passkey (mfa) or if a user with no passkey has used password to login. Returns false if a user with passkey has used password to login. With hasPasskey the ldap attribute irfAuthorizedService is checked. With hassPasskey2 database is directly checked if user has passkey registered.
-->
    <bean id="shibboleth.context-check.Condition" parent="shibboleth.Conditions.OR">
        <constructor-arg>
            <list>
                <bean class="net.shibboleth.idp.profile.logic.SimpleAttributePredicate"
                        p:useUnfilteredAttributes="true">
                    <property name="attributeValueMap">
                        <map>
                            <entry key="hasPasskey">
                                <list>
                                    <value>no</value>
                                </list>
                            </entry>
                        </map>
                    </property>
                </bean>
                <ref bean="CheckForMFA" />
            </list>
        </constructor-arg>
    </bean>

<!-- Checks all the active authentication results for the appropriate AuthnContextClassRefPrincipal. -->
    <bean id="CheckForMFA" parent="shibboleth.Conditions.Scripted" factory-method="inlineScript">                    
        <constructor-arg>
            <value>
                <![CDATA[
                        value = false;
                        principalType = Java.type("net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal");
                        subjectCtx = input.getSubcontext("net.shibboleth.idp.authn.context.SubjectContext");
                        if (subjectCtx != null) {
                                var subjectIter = subjectCtx.getSubjects().iterator();
                                while (!value && subjectIter.hasNext()) {
                                    var princIter = subjectIter.next().getPrincipals(principalType.class).iterator();
                                    while (!value && princIter.hasNext()) {
                                        if (princIter.next().getName() == "https://refeds.org/profile/mfa") {
                                            value = true;
                                        }
                                }
                            }
                        }
                        value;
                ]]>
            </value>
        </constructor-arg>
    </bean>
</beans>
```

intercept/relying-party.conf:
```
<bean id="shibboleth.DefaultRelyingParty" parent="RelyingParty">
<property name="profileConfigurations">
	<list>
		<bean parent="SAML2.SSO" p:postAuthenticationFlows="#{ {'terms-of-use', 'attribute-release', 'context-check'} }" />
	    <ref bean="SAML2.ECP" />
	    <ref bean="SAML2.Logout" />
	    <ref bean="SAML2.AttributeQuery" />
	    <ref bean="SAML2.ArtifactResolution" />
	    <ref bean="OIDC.SSO" />
	    <ref bean="OIDC.UserInfo"/>
	    <ref bean="OAUTH2.Token"/>
	    <ref bean="OAUTH2.Revocation"/>
	    <ref bean="OAUTH2.Introspection" />
	</list>
</property>
</bean>
```

TBC