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:
<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>
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
,
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()
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 useremail
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);
})();
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.
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."}]}