Skip to main content
Version: 2.1

Nevis Component Configuration Examples

This appendix provides the configuration snippets for the Nevis components required to cover the described use cases.

All use cases require the configuration of nevisProxy, nevisAuth, nevisFIDO and nevisIDM. Read the snippets carefully to ensure a correct configuration.

nevisProxy

Use Case Independent Configuration

Registration

Authentication

nevisAuth

Use Case Independent Configuration

Registration

Authentication

In the esauth4.xml you need the following AuthState:

Configure ScriptState for FIDO2 Authentication
<AuthState name="<some-name>" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<ResultCond name="ok" next="<your-next-state>"/>
<Response value="AUTH_CONTINUE">
<Gui name="fido2_auth" label="title.login"/>
</Response>
<property name="parameter.fido" value="<fido_host:fido_port>"/>
<property name="script" value="file:///var/opt/nevisauth/<instance>/conf/fido2_auth.groovy"/>
</AuthState>
info

Set the correct AuthState for the ok transition depending on your integration scenario.

The fido2_auth.groovy looks as follows, place it in the folder configured in the auth state, by default /var/opt/nevisauth/<instance>/conf/fido2_auth.groovy,

FIDO2 Authentication Groovy Script
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper

def showGui() {
response.setGuiName('fido2_auth') // fixed name is the trigger for including the fido2_auth.js
response.setGuiLabel('title.login') // required so that GUI can be shown
response.addInfoGuiField('info', 'info.login', null) // no value required
if (notes.containsKey('lasterrorinfo') || notes.containsKey('lasterror')) { // displayed if authentication fails
response.addErrorGuiField('lasterror', notes['lasterrorinfo'], notes['lasterror'])
}
// user extId must be present in the session, e.g. set by a IdmGetPropertiesState
def userExtId = session['ch.nevis.idm.User.extId']
Objects.requireNonNull(userExtId) // will produce a system error if missing (500 error page)
response.addHiddenGuiField('userExtId', '', userExtId) // required by JS
}

if (!inargs.containsKey('id')) { // form post not received
showGui()
return
}

def baseUrl = parameters.get('fido')
def post = new URL("https://${baseUrl}/nevisfido/fido2/assertion/result").openConnection()
post.setRequestMethod("POST")
post.setRequestProperty("Content-Type", "application/json")
post.setDoOutput(true) // required to write body

def json = new JsonBuilder()
json {
"id" inargs['id']
"type" inargs['type']
response {
"clientDataJSON" inargs['response.clientDataJSON']
"authenticatorData" inargs['response.authenticatorData']
"signature" inargs['response.signature']
"userHandle" inargs['response.userHandle']
}
}

post.getOutputStream().write(json.toString().getBytes("UTF-8"))

def responseCode = post.responseCode
def responseText = post.inputStream.text

LOG.info('<== Response: ' + responseCode + ": " + responseText)

if (responseCode == 200) {
def status = new JsonSlurper().parseText(responseText).status
if (status == 'ok') {
response.setResult('ok')
return
}
}

response.setError(1, "FIDO2 authentication failed")
showGui()
Setting the extId

Note that the Groovy script requires a session variable ch.nevis.idm.User.extId.

This variable should be set by an IdmGetPropertiesState by fetching the extId of the user.

A good configuration must always contain an IdmGetPropertiesState.

Using the loginId is not recommended as it could be either of:

  • loginId of nevisIDM user
  • email of nevisIDM user

The extId of the nevisIDM user more precisely defined.

nevisLogRend

Authentication

The following adaption to the Velocity template will ensure the fido2_auth.js is included by the nevisLogrend template when the GUI fido2_auth is rendered as set in the Groovy script:

#if ($gui.name == "fido2_auth")
<script src="${login.appDataPath}/resources/base64.js"></script>
<script id="fido2_auth"
src="${login.appDataPath}/resources/fido2_auth.js"
data-userExtId="$gui.getGuiElem("userExtId").value">
</script>
#end

The fido2_auth.js looks as follows:

(function() {

function authenticate(userExtId) {

const request = {};
request.username = userExtId;

//this request currently does _not_ go through nevisAuth, this will change in the future to handle
//missing user scenarios
fetch("/nevisfido/fido2/attestation/options", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
}).then(res => {
res.json().then(options => {
options.challenge = base64url.decode(options.challenge);
options.allowCredentials = options.allowCredentials.map((c) => {
c.id = base64url.decode(c.id);
return c;
});
assertionResult(options, userExtId);
});
}).catch((err) => console.error("error: ", err));
}

function addInput(form, name, value) {
const input = document.createElement("input");
input.name = name;
input.value = value;
form.appendChild(input);
}

async function assertionResult(options, userExtId) {

const assertion = await navigator.credentials.get({ "publicKey": options });

const form = document.createElement("form");
form.method = "POST";
form.style.display = "none";

addInput(form, "id", assertion.id);
addInput(form, "type", assertion.type);
addInput(form, "response.clientDataJSON", base64url.encode(assertion.response.clientDataJSON));
addInput(form, "response.authenticatorData", base64url.encode(assertion.response.authenticatorData));
addInput(form, "response.signature", base64url.encode(assertion.response.signature));
addInput(form, "response.userHandle", base64url.encode(new TextEncoder().encode(userExtId).buffer));

document.body.appendChild(form);

form.submit();
}

let userExtId = document.querySelector('[data-userExtId]').getAttribute('data-userExtId');
authenticate(userExtId);
})();

Using form submit instead of HTTP POST with JSON payload

The assertionResult is sent to the backend using form post instead of posting the payload using the JSON format and an ajax call as you can see in the above code.

The reason for using form submit is to be able to handle nevisAuth authentication scenarios better in scenarios where the Nevis backend will send back an HTML or requesting a browser redirect. As with using the form post, the browser will handle the response the implementation of the client JavaScript does not require complex handling of different response payloads and/or formats.

nevisFIDO

Use Case Independent Configuration

Registration

Authentication

nevisIDM

Use Case Independent Configuration

nevisIDM require the unit policy to allow credential type 23 which represents the FIDO2 Authenticator or else FIDO2 operations will fail as the user won't be allowed to have the credential type.

info

In case the credential type is not allowed in the unit policy, the following ERROR log message will be shown:

422 Unprocessable Entity HTTP/1.1' with content: '{"errors":[{"code":"errors.CredTypeUnitPolicyViolated","message":"credential type '23' (FIDO2 Authenticator) is not allowed for unit of user 'xxxx'. Hint: check Unit Policies."}]}