diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml
index ba491a8..5e23020 100644
--- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml
+++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml
@@ -45,7 +45,7 @@ spec:
   podDisruptionBudget:
     maxUnavailable: "50%"
   git:
-    tag: "r-5fa3629fafa6609b3d40f948feeec493651fe174"
+    tag: "r-9af4078cd1befd57d74704e77388710ec33873d4"
     dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth"
     credentials: "git-credentials"
   keystores:
diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/Recovery_getCredentials.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/Recovery_getCredentials.groovy
deleted file mode 100644
index 1e916ec..0000000
--- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/Recovery_getCredentials.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-import ch.nevis.idm.client.IdmRestClient
-import ch.nevis.idm.client.IdmRestClientFactory
-import groovy.json.JsonSlurper
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-import java.time.ZoneId
-import ch.nevis.esauth.auth.engine.AuthResponse
-import groovy.xml.XmlSlurper
-
-IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
-
-String baseUrl = parameters.get('baseUrl')
-String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
-String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
-String endPoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId/fido2"
-String endPointFidoUAF = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId/generic-credentials"
-
-def userDto = new XmlSlurper().parseText(session['ch.adnovum.nevisidm.userDto'])
-def hasRecoveryRole = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'recovery' }
-if (hasRecoveryRole != null) {
-  String result
-  try {
-     result = idmRestClient.get(endPoint)
-     resultFidoUAF = idmRestClient.get(endPointFidoUAF)
-
-  def json = new JsonSlurper().parseText(result)
-LOG.info('Result fido2: ' + json)
-
-    def login=false
-    json['items'].each {
-     if ("active".equals(it.stateName)) {
-         response.setSessionAttribute('agov.recovery.securityKey', it.userFriendlyName)
-         response.setResult('loginWithFido2')
-         login=true
-         return
-      }
-
-   }
-   if (login) {
-     return
-   }
-   def jsonFidoUAF = new JsonSlurper().parseText(resultFidoUAF)
-   LOG.info('Result fidoUAF: ' + jsonFidoUAF)
-      jsonFidoUAF['items'].each {
-        if ("active".equals(it.stateName)) {
-          response.setSessionAttribute('agov.recovery.accessapp', it.properties.fidouaf_name)
-          response.setSessionAttribute('agov.recovery.accessapp.dispatchTargetId', it.identification.replaceAll('dispatch_target_', ''))
-          response.setResult('loginWithFidoUAF')
-          login=true
-          return
-        }
-     }
-     if (login) {
-       return
-     }
-  } catch(Exception e) {
-    LOG.error(e.toString())
-    response.setResult('failed')
-   return
-  }
-
-}
-response.setResult('ok')
\ No newline at end of file
diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml
index c69dd44..cfd458a 100644
--- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml
+++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml
@@ -119,7 +119,7 @@
             
         
         
-        
+        
             
             
         
@@ -2103,7 +2103,7 @@
             
             
             
-            
+            
             
             
                 
@@ -2117,6 +2117,10 @@
                 
             
             
+            
+            
+            
+            
             
             
             
@@ -2230,28 +2234,26 @@
             
             
         
-        
+        
             
-            
+            
             
             
             
             
             
-            
+            
             
-            
+            
                 
-                
+                
             
             
-            
+            
             
-            
+            
             
-            
-            
-            
+            
         
         
             
@@ -2330,7 +2332,7 @@
         
         
             
-            
+            
             
             
             
@@ -2340,7 +2342,7 @@
         
         
             
-            
+            
             
             
             
@@ -2419,7 +2421,7 @@
             
             
             
-            
+            
             
             
                 
@@ -2447,6 +2449,14 @@
             
             
         
+        
+            
+            
+            
+            
+            
+            
+        
         
             
             
diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery-processing.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery-processing.groovy
index 3076e78..6dce281 100644
--- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery-processing.groovy
+++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery-processing.groovy
@@ -1,5 +1,9 @@
-import org.codehaus.groovy.runtime.StackTraceUtils
+import groovy.json.JsonSlurper
 import groovy.xml.XmlSlurper
+import org.codehaus.groovy.runtime.StackTraceUtils
+
+import ch.nevis.idm.client.IdmRestClient
+import ch.nevis.idm.client.IdmRestClientFactory
 
 
 // AGOVaq conversion
@@ -77,6 +81,45 @@ def getUserMustRecoverValidFrom() {
   return (authzNode) ? ((authzNode.validFrom && !authzNode.validFrom.text().isEmpty()) ? authzNode.validFrom?.text() : authzNode.ctlCreDat?.text()) : ''
 }
 
+def userHasNewLoginFactor() {
+  IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
+
+  String baseUrl = parameters.get('baseUrl')
+  String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
+  String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
+  String baseEndPoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId"
+
+  def result = false
+  response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
+
+  try {
+     def credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/generic-credentials"))
+
+     def accessApp = credInfoArray['items'].find( it -> it.stateName == "active")
+     if (accessApp) {
+       result = true;
+       response.setSessionAttribute('agov.recovery.accessapp', accessApp.properties.fidouaf_name)
+       response.setSessionAttribute('agov.recovery.accessapp.dispatchTargetId', accessApp.identification.replaceAll('dispatch_target_', ''))
+       response.setSessionAttribute('agov.recovery.newLoginFactor', 'ACCESS_APP')
+       return
+     }
+
+     credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/fido2"))
+
+     def fido2Key = credInfoArray['items'].find( it -> it.stateName == "active")
+     if (fido2Key) {
+       result = true;
+       response.setSessionAttribute('agov.recovery.securityKey', fido2Key.userFriendlyName)
+       response.setSessionAttribute('agov.recovery.newLoginFactor', 'FIDO2')
+       return
+     }
+
+  } catch(Exception e) {
+    LOG.error(e.toString())
+  }
+  return result
+}
+
 
 // for autditing
 def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
@@ -95,14 +138,14 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
     LOG.debug("Recovery: Dto is '${userDto}")
     LOG.debug("Recovery: state is '${userState}")
     LOG.debug("Recovery: RecoveryCode is '${recoveryCode ? recoveryCode : 'none'}'") 
-    def session = request.getAuthSession(true)
 
     if (userState == 'ACTIVE') {
 
-      session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
-      session.setAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:email')
-      session.setAttribute('agov.recovery.codeStatus', 'notNeeded')
-      session.setAttribute('agov.recovery.codeDetailStatus', 'n/a')
+      response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
+      response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:email')
+      response.setSessionAttribute('agov.recovery.codeStatus', 'notNeeded')
+      response.setSessionAttribute('agov.recovery.codeDetailStatus', 'n/a')
+      response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
 
       def maxLoiList =   userDto.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-Loi' }.collect({ node -> node.name.text() })
       maxLoi = (maxLoiList == null || maxLoiList.isEmpty()) ? null : maxLoiList.sort().last()
@@ -111,7 +154,7 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
       def agovAqValidFrom = null
       if (maxLoi) {
         if (maxLoi != 'level100') {
-          session.setAttribute('agov.recovery.codeDetailStatus', '' + maxLoi)
+          response.setSessionAttribute('agov.recovery.codeDetailStatus', '' + maxLoi)
         }
 
         idVerification = userDto.'**'.find { node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,' + maxLoi}?.value?.text()
@@ -124,11 +167,12 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
 
       def hasRecoveryRole = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'recovery' }
 
+      def hasNewLoginFactor = hasRecoveryRole && userHasNewLoginFactor()
 
       if (mustRecover) {
         // attributes are defined over the mustRecover authorization
-        session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
-        session.setAttribute('agov.recovery.codeDetailStatus', 'mustRecover')
+        response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
+        response.setSessionAttribute('agov.recovery.codeDetailStatus', 'mustRecover')
 
         idVerification = getUserIdVerificationForRecovery(maxLoi ?: 'level100') ?: idVerification
 
@@ -142,6 +186,7 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
       LOG.debug("Recovery: agovAqValidFrom is ${agovAqValidFrom}")
       LOG.debug("Recovery: mustRecover is '${mustRecover}'")
       LOG.debug("Recovery: hasRecoveryRole is '${hasRecoveryRole}'")
+      LOG.debug("Recovery: hasNewLoginFactor is '${hasNewLoginFactor}'")
 
       if (maxLoi != null) {       
         if (maxLoiRoleToCtxClssConvertorMap.containsKey(maxLoi)) {
@@ -150,7 +195,7 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
           response.setSessionAttribute('agov.recovery.currentIdVerification', '' + idVerification)
           response.setSessionAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + agovAqValidFrom)
 
-          if ((maxLoi == 'level100') && (mustRecover == null)) {
+          if ((maxLoi == 'level100') && (mustRecover == null) && !hasNewLoginFactor) {
             // AQ100 accounts need to use the recovery code, if they can
             // check the status of recoveryCode credential
             if (recoveryCode && !blockingCredentialStates.contains(recoveryCode.state.text())) {
@@ -158,13 +203,20 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
               response.setResult('needCode')
               return
             } else {
-             LOG.warn("AGOVaq100 recovery: skipped Recovery-Code check '${recoveryCode ? recoveryCode.state.text() : 'MISSING'}'")
-              session.setAttribute('agov.recovery.codeStatus', 'skipped')
-              session.setAttribute('agov.recovery.codeDetailStatus', "unusable (state: ${recoveryCode ? recoveryCode.state.text() : 'MISSING'})")
+              LOG.warn("AGOVaq100 recovery: skipped Recovery-Code check '${recoveryCode ? recoveryCode.state.text() : 'MISSING'}'")
+              response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
+              response.setSessionAttribute('agov.recovery.codeDetailStatus', "unusable (state: ${recoveryCode ? recoveryCode.state.text() : 'MISSING'})")
               response.setResult('ok')
               return
             }
 
+          } else if ((maxLoi == 'level100') && hasNewLoginFactor) {
+            LOG.debug("Recovery: new Login Factor")
+            response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
+            response.setSessionAttribute('agov.recovery.codeDetailStatus', "new login factor already registered (${session['agov.recovery.newLoginFactor']})")
+            response.setResult('ok')
+            return
+
           } else {
             LOG.debug("Recovery: email")
             response.setResult('ok')
diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery_fido2_auth.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery_fido2_auth.groovy
index 188ab89..5ed2b89 100644
--- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery_fido2_auth.groovy
+++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/recovery_fido2_auth.groovy
@@ -8,7 +8,6 @@ if (inargs.containsKey('cancel_fido2')) {
 
 def showGui() {
     response.setGuiName('recovery_fidokey_auth') // name is the trigger for including the JS
-    //response.setGuiName('fido2_auth') // name is the trigger for including the JS
     response.setGuiLabel('title.login.fido2')
     response.addInfoGuiField('info', 'info.login.fido2', null)
     response.addHiddenGuiField('authRequestId', 'not used', session['ch.nevis.auth.saml.request.id'])
@@ -18,7 +17,6 @@ def showGui() {
         response.addErrorGuiField('lasterror', notes['lasterrorinfo'], notes['lasterror'])
     }
     if (parameters.containsKey('cancel')) {
-        // TODO koenig 20221021: replace with specific label
         response.addButtonGuiField('cancel_fido2', 'cancel.login.fido2.button.label', 'true')
     }
 }
@@ -42,12 +40,14 @@ def post(connection, json) {
     connection.getOutputStream().write(body.getBytes())
 }
 
-String userExtId = session['ch.adnovum.nevisidm.user.extId'] ?: session['ch.nevis.idm.User.extId'] ?: request.getUserId() ?: notes['userid']
+String userExtId = session['ch.adnovum.nevisidm.user.extId'] ?: session['ch.nevis.idm.User.extId']
 if (userExtId == null) {
     LOG.error("missing extId of nevisIDM user. check your authentication flow.")
+    notes.setProperty('lasterror', '1')
+    notes.setProperty('lasterrorinfo', 'missing extId of nevisIDM user')
+    response.setResult('error')
+    return
 }
-// without the user extId this script won't work and we can fail with a System Error
-Objects.requireNonNull(userExtId)
 
 def path = getPath()
 if (path == null) {
@@ -65,32 +65,17 @@ if (path == '/nevisfido/fido2/attestation/options') {
     }
     post(connection, json)
     def responseCode = connection.responseCode
-// account without FIDO2 case
+
     if (responseCode == 400) {
-      def responseText = '''{"status": "ok",
-	                         "errorMessage": "",
-	                         "fido2SessionId": "270312ae-8d74-4ded-ad89-5310da2d2e6f",
-	                         "challenge": "tKCqUM6URnykri1ZFz-3ww",
-	                         "timeout": 300000,
-	                         "rpId": "agov-d.azure.adnovum.net",
-	                         "allowCredentials": [
-		                     {
-			                    "type": "public-key",
-			                    "id": "WVzzUwxOf-1doTGkrdRHWPDbETTawkULLPsEiwiQwA2AFC4_YgL5OVmJJOT2OulAZSq_tvOfNlMSRKRXyXH2kw",
-			                    "transports": []
-		                     }
-	                                             ],
-	                         "userVerification": "preferred"}'''
-	  LOG.info("<== Response: ${responseCode}")
-      response.setContent(responseText) // return response from nevisFIDO "as-is"
-      response.setContentType('application/json')
-      response.setHttpStatusCode(200)
-      response.setIsDirectResponse(true)
-      return
+        LOG.error("FIDO2 options call failed for '${userExtId}'")
+        notes.setProperty('lasterror', '1')
+        notes.setProperty('lasterrorinfo', 'missing extId of nevisIDM user')
+        response.setResult('error')
+        return
     }
 
     def responseText = connection.inputStream.text
-    LOG.info("<== Response: ${responseCode} : ${responseText}")
+    LOG.debug("<== Response: ${responseCode} : ${responseText}")
     response.setContent(responseText) // return response from nevisFIDO "as-is"
     response.setContentType('application/json')
     response.setHttpStatusCode(200)
@@ -100,21 +85,6 @@ if (path == '/nevisfido/fido2/attestation/options') {
 
 if (path == '/nevisfido/fido2/assertion/result') {
 
-  if (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) {
-    // wrong request, "force" a timeout
-    LOG.info('authentication timeout enforced, due to concurrent requests')
-
-    response.setIsDirectResponse(true)
-    response.setContentType('text/html; charset=UTF-8')
-    response.setContent('Timeout')
-    response.setHttpStatusCode(205)
-    response.setHeader('IDP-AUTH', 'Timeout')
-
-    // CONTINUE to keep the other request beeing processed
-    response.setStatus(AuthResponse.AUTH_CONTINUE)
-    return
-  }
-
     def userHandleValue = userExtId.getBytes().encodeBase64Url().toString()
     LOG.info("encoded userHandle: ${userHandleValue}")
     json {
@@ -132,15 +102,13 @@ if (path == '/nevisfido/fido2/assertion/result') {
     // test if credentials exist
     if (responseCode != 400) {
         def responseText = connection.inputStream.text
-        LOG.info("<== Response: ${responseCode} : ${responseText}")
+        LOG.debug("<== Response: ${responseCode} : ${responseText}")
         if (responseCode == 200 && new JsonSlurper().parseText(responseText).status == 'ok') {
+            response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:fido')
             response.setResult('ok')
             return
         }
     }
-    //response.setHttpStatusCode(400)
-    //response.setIsDirectResponse(true)
-    // DEFINE how to handel error
     notes.setProperty('lasterror', '1')
     notes.setProperty('lasterrorinfo', 'FIDO2 authentication failed')
     response.setResult('error')