IBM Security Global Forum

ISAM FIDO2 – Using the FIDO2 server endpoints (Part 4 of 4)

By Shane Weeden posted Thu July 01, 2021 12:00 AM


This article is the fourth in a technical series on configuring and using FIDO2 capabilities in ISAM 9.0.7.
If you haven’t already done so, please work through these previous articles as the information and system that is prepared as part of them will be assumed knowledge when reading this one…

In this article I will be covering the use of the JSON/HTTP web services interface to the ISAM FIDO2 server. The purpose of this interface is to provide APIs for a trusted relying-party application to facilitate registration and authentication ceremonies. Note that the term “registration” shall be used interchangeably with “attestation” in this article, and the term “authentication” shall be used interchangeably with “assertion”. FIDO2 makes use of the attestation and assertion terminology throughout the specifications. We say that the relying-party application is trusted because it is going to be making authenticated calls to the ISAM FIDO2 server endpoints on behalf of users that it has authenticated outside the context of ISAM.

Let’s first take a look at an architecture diagram showing the general FIDO2 ecosystem in ISAM:


Thus far the blog tutorials I have written have covered the general configuration and use of the WebAuthn Relying-Party (RP) and FIDO2 authentication policy services within ISAM itself. These in turn leverage the IBM Security FIDO Service 2.0 component which interface with the configuration and DB storage services in ISAM. The “client” to all these ISAM services has been a browser, which receives HTML+Javascript from the WebAuthn RP components. The Javascript calls the WebAuthn APIs provided by the browser (navigator.credentials.create and navigator.credentials.get) for attestation and assertion operations.

In this article we are going to show how you can write your own FIDO2 WebAuthn RP application, more as an exercise for understanding the FIDO2 server endpoint interfaces for API clients than as something you are likely to actually deploy. After all, why wouldn’t you just use the built-in ISAM WebAuthn RP capabilities in the first place! Typically you would use ISAM’s WebAuthn RP, but learning about how these web services interfaces work via an externally developed WebAuthn RP implementation is a good first step toward understanding how you might then apply them to a different type of RP application such a native mobile application backend. I hope to cover native-mobile in a future article, however this article is going to lay the groundwork for that exercise.

Let’s look at the architecture for developing an external WebAuthn RP application which uses ISAM “as-a-service”:

The goal here is to develop the External RP – in this case for browser clients using HTML/JS with calls to the WebAuthn APIs. The rest of this article is going to take you through the ISAM configuration necessary to do this, a discussion of the ISAM FIDO2 server endpoints, and the install/run of a demonstration Node.js application that has been written to implement the external RP.

Host Configuration

The external WebAuthn RP will be deployed with the logical hostname, and for the purposes of this exercise we will run it in a docker image under the same docker-compose setup as was used for the initial lab. I’ve chosen to have the system listen on the same IP address already assigned for, however the new RP will listen on port 9443 so there is no conflict. You should update your local machine (where docker / docker-compose and your browser is running) to include as shown:


Required ISAM Configuration

Our external RP application is going to make use of a number of services from ISAM. Some of these are already configured, and some need to be set up. Here’s a summary of what we need to do:

  • Create an ISAM user to act as the service user for OAuth authentication between the external WebAuthnRP and ISAM
  • Create and configure an OAuth definition and client.
  • Configure the existing ISAM Web Reverse Proxy for OAuth and test OAuth authentication.
  • Configure and test SCIM.
  • Add a new FIDO2 RP definition for the RP ID


Creating the ISAM service user

When the external WebAuthn RP application connects to ISAM services, it will be acting in the role of a trusted administrative client. For example it will be retrieving and modifying user profile information via SCIM, making calls to the FIDO2 service endpoints on behalf of locally authenticated users, etc. Both SCIM and FIDO2 server endpoints have requirements that operations for users “other than yourself” need to be made with an authenticated context of a user that is in a special-purposes administrative group. The default name for this group is adminGroup in ISAM, and we need to both create that group and the service user who is a member of that group.

The following pdadmin commands are used to set up the group and user (which will also be the OAuth client) for authenticated requests to the FIDO2 server and SCIM endpoints. We can execute these remotely on the iamlab_isamconfig_1 as shown:


$ docker exec -i iamlab_isamconfig_1 pdadmin -a sec_master -p Passw0rd <<-EOF
user create fido2client cn=fido2client,dc=ibm,dc=com fido2 client AnySecretThingYouWant!
user modify fido2client account-valid yes
group create adminGroup cn=adminGroup,dc=ibm,dc=com adminGroup
group modify adminGroup add fido2client

The password for the fido2client user is actually not used (although you should set it to something non-guessable) because this user will be authenticating via OAuth, as we shall see shortly.


Creating the OAuth definition

The OAuth definition is used for the purposes of allowing the fido2client (a service user used by the external WebAuthn RP) to obtain an access token via the client_credentials OAuth grant type, and then use that access token to make authenticated requests to ISAM API services.

Navigate to Secure Access Control -> OpenID Connect and API Protection, then create a definition as shown below. Note that the only enabled grant type is Client credentials and all other values are default.

Feel free to expand the Token Management section and increase the lifetime of access tokens to more than an hour if you like. It will help with issues related to token expiry as you work through this article.


Navigate to the Mapping Rules tab and change the pre-token mapping rule to set limit_method = “lru”. This is important as we are only using one OAuth client instance, and as it provisions new access tokens via the client credentials grant type flow we want to discard old access tokens rather than block the acquisition of new tokens.


Finally create the OAuth client fido2client matching the ISAM user account we created earlier:

Deploy all changes you have made before continuing.


Configure the existing ISAM Web Reverse Proxy for OAuth and test OAuth authentication

The next step is to enable the Web Reverse Proxy to be the front-end for both the OAuth token endpoint, and act as a resource server, accepting authentication via OAuth access tokens. This is done with the management console, as shown below. Note that the AAC runtime is accessed via the docker network hostname isamruntime on port 443, and the easuser password is Passw0rd (per the original ISAM docker lab).


One of the actions of the configuration wizard is to turn off forms authentication to the Web Reverse Proxy. In our case we actually want the same Web Reverse Proxy to support both OAuth authentication  (for API clients) and forms authentication (for browser-based users) concurrently, so edit the WRP configuration and turn forms-auth back on!


Deploy all changes, then publish the container configuration. Wait a moment until the runtime and wrp containers pick up the new configuration snapshot (I monitor the container logs to watch this happening).

Now lets test the OAuth client credentials flow as the fido2client oauth client:

$ curl -k -H "Accept: application/json" -d "grant_type=client_credentials&client_id=fido2client&client_secret=Passw0rd"


You should see a response similar to the one above. Using your own access token value as the Bearer header value, try accessing the diag resource (this time we’ll use a non-HTML plaintext version of the credential viewer diagnostic tool) using curl:

$ export AT="9AVwm6bQOvP49dZMi7hv"

$ curl -k -H "Authorization: Bearer $AT" -H "Accept: application/json"

You should see plain text content returned, with attributes under the Access Manager Credential section indicating you have successfully authenticated via oauth authentication as the fido2client user:

Username: fido2client

Access Manager Credential:

group[0]: adminGroup
expires[0]: 2019-07-04T04:54:46Z
tagvalue_user_session_id[0]: aXNhbWNvbmZpZy1ycDEA_XR14lwAAAIAAAAA4l3gdXRibA9RvfwAAUVhWMGFHOXlhWHBoZEdsdmJqMUNaV0Z5WlhJZ2FXOXlOREZ0UlV0dmVXWjBURk5UZGpVek1IQUE=:default
AZN_CRED_AUTHNMECH_INFO[0]: OAuth Authentication
client_type[0]: confidential
AZN_CRED_GROUP_REGISTRY_IDS[0]: cn=adminGroup,dc=ibm,dc=com
tagvalue_session_index[0]: 80bf2bb0-9e0f-11e9-be88-0242ac170008
authorized[0]: TRUE
AZN_CRED_PRINCIPAL_UUID[0]: 98a669f8-9a1f-11e9-90bf-0242ac1f0007
oauth_token_client_id[0]: fido2client
AZN_CRED_GROUP_UUIDS[0]: cc7c3770-9a1b-11e9-90bf-0242ac1f0007
AZN_CRED_AUTHZN_ID[0]: fido2client
AZN_CRED_REGISTRY_ID[0]: cn=fido2client,dc=ibm,dc=com
tagvalue_login_user_name[0]: fido2client
tagvalue_max_concurrent_web_sessions[0]: unset
access_token[0]: ior41mEKoyftLSSv530p
AZN_CRED_GROUPS[0]: adminGroup
AZN_CRED_VERSION[0]: 0x00000907
AZN_CRED_BROWSER_INFO[0]: curl/7.54.0
username[0]: fido2client


Configure and test SCIM

The SCIM service in ISAM is going to be used for user-self-care lifecycle management of FIDO2 registrations. Specifically, we are going to use SCIM to allow the retrieval of the list of existing FIDO2 registrations for a user, as well as allowing the user to delete a registration.

For a more in-depth look at SCIM configuration, feel free to take a look at section 7 of the IBM Verify Cookbook. In this article I’m just going to take you through the steps.


First configure an ISAM Runtime server connection. This will be used by the SCIM configuration.

Now let’s configure the SCIM service, which uses this LDAP server connection. Don’t change any of the defaults on the General tab – this diagram only shows the tabs and items that need your attention. Note that because we have not set up our LDAP users to use the givenName field, I have changed this to use cn instead.


Finally let’s create a dedicated transparent-path junction for SCIM access, and attach ACL policy to it so that only the adminGroup users have access to the /scim/Users endpoint. This can be done via pdadmin on the configuration container:

$ docker exec -i iamlab_isamconfig_1 pdadmin -a sec_master -p Passw0rd <<-EOF
server task rp1-webseald-isamconfig create -t ssl -c all -x -h isamruntime -p 443 -B -U easuser -W Passw0rd /scim
acl attach /WebSEAL/isamconfig-rp1/scim isam_authsvc_nobody
acl create scim-admins
acl modify scim-admins set user sec_master TcmdbsvaBRrxl
acl modify scim-admins set group iv-admin TcmdbsvaBRrxl
acl modify scim-admins set group adminGroup Tmdr
acl modify scim-admins set any-other T
acl modify scim-admins set unauthenticated T
acl attach /WebSEAL/isamconfig-rp1/scim/Users scim-admins


Whilst not strictly necessary for the requirements of this deployment, we should also update the Web Reverse Proxy configuration file to handle URL re-writing for SCIM responses. Update the WRP configuration file for script filtering, as shown:



Deploy any pending changes, and publish the configuration snapshot at this point. After services restart, test getting emily’s SCIM record, using the fido2client and oauth authentication for access:


$ curl -k -H "Authorization: Bearer $AT" -H "Accept: application/scim+json" ""



Did you notice the FIDO2 registration in the SCIM results?!

Note: If you get a login form response back from the curl command, chances are your access token has expired and you should get a new one using the client_credentials grant type flow we performed earlier.


Create FIDO2 RP definition for


In earlier articles in this series we have been working from the FIDO2 relying-party definition for, but this time round we will be using a different website (, and as such we must configure a different relying-party definition. We’ll use the advanced wizard this time as we want to include metadata as we go and ensure the origin URL is going to include port 9443 as that’s where our Node.js application is going to run. Note the diagrams below only show the panels where changes need to be made. Be sure to click through all the panels in the wizard and save the configuration at the end. Notice this time we won’t employ a mediator. Our simple external WebAuthn RP doesn’t require the functionality it offers. Also be particularly careful to ensure that the allowed origin URL is


Deploy and pending changes, and publish the configuration. That completes all the necessary updates to ISAM.

When services restart, lets use the command-line management APIs to do some discovery of the FIDO2 relying-party configuration and start understanding how the web services endpoints work…


Introducing the ISAM FIDO2 Server Endpoints

As the LMI administrator, let’s retrieve the complete details of our FIDO2 relying party definitions:

$ curl -k --user "admin:Passw0rd" -H "Accept: application/json"

I have reformatted the output a little for analysis.







Notice that we have two relying-party definitions – one for the rpid and the other for What is of interest to us at this point is the id field for the definition with rpId In my case that value is: c32c110d-d720-44d6-ab48-e8ff8c8f6b84

This is important because the FIDO2 server endpoints exposed by ISAM are of the general form:

  • https://[wrp_hostname]/[junction]/sps/fido2/[rp_config_id]/attestation/options
  • https://[wrp_hostname]/[junction]/sps/fido2/[rp_config_id]/attestation/result
  • https://[wrp_hostname]/[junction]/sps/fido2/[rp_config_id]/assertion/options
  • https://[wrp_hostname]/[junction]/sps/fido2/[rp_config_id]/assertion/result

For our new RP, the four FIDO2 server endpoints will therefore be:


Using your own RP’s configuration ID, test that as the administrative fido2client user, via oauth, you can retrieve FIDO2 attestation options for emily:

$ export RPCONFIGID=c32c110d-d720-44d6-ab48-e8ff8c8f6b84

$ curl -k -H "Authorization: Bearer $AT" -d '{"username":"emily"}' "$RPCONFIGID/attestation/options"

If everything is working as expected, you should get back configuration options like this:



This is precisely what the registration operation does in the ISAM device management page that we used in the first article in this series to initiate registration. If you don’t believe me, open your browser developer tools and watch the XHR requests in the network monitor as you navigate to the device management page and press register (

Note that if the fido2client user (i.e. the authenticated user making the request to the /attestation/options endpoint) was not in the adminGroup, then the request for options for username "emily" (or any user other than fido2client) would fail. The server endpoint verifies that options requests are either made “for yourself” or that if the request body contains a username other than the authenticated user making the request, then the authenticated user making the request must be in the administrative group configured for the ISAM relying party. This explains the purposes for the administrative group in a relying party configuration, and why I said at the start of this article that the external WebAuthn RP application is a trusted client to the FIDO2 server endpoints.

In the registration network trace example shown above, the browser is making the call to /attestation/options via AJAX, which uses the browser cookie, so the authenticated request is as the user "emily".


Recall that I mentioned above there are four server endpoints – two for attestation (aka registration), and two for assertion (aka authentication). The “options” endpoints are used to retrieve a server challenge (with additional options), and the “result” endpoints are used to return the responses generated by the FIDO2 client (which in our case is currently the browser that we are using). Our Node.js application is going to call these endpoints at the request of operations being performed in the browser – watch for that later on.

The web services schema for the ISAM FIDO2 server endpoints are largely based on a non-normative specification from the FIDO alliance that is used for testing FIDO2 servers. You can find that specification here:

There are a few very minor additions to behaviour in ISAM over and above what this specification defines – mostly related to supporting the username-less login use case:

  • It is permitted to pass "username" as the empty string ""  in /assertion/options calls. This indicates to the ISAM FIDO2 server that assertion options are being requested for a username-less login
  • In successful /assertion/result responses, ISAM will include information about the user who registered the authenticator that the successful assertion result was supplied by. This allows a relying party to determine who the user is that is being logged in for username-less login use cases.
  • If an /attestation/options or /assertion/options call does not contain the "username" field at all, then the ISAM server will assert it as the username of the authenticated user making the request. If you were to look at the payload of an example /attestation/options request from the device management page (like the example above) you will see that the payload does not contain a username field, and it will therefore be asserted by ISAM as whoever is logged in.
  • In either /attestation/result or /assertion/result call it is possible to supply “extra information” in the response body. This is done via the mediator, using either credentialData or responseData context variables to set additional attributes. We saw how the built-in ISAM WebAuthn authentication mechanism uses credentialData in an earlier article to augment the ISAM credential during login. Programmatic clients to the FIDO2 server endpoints will receive this data in the JSON payload and can do with it whatever they wish. You can read more about using credentialData and responseData in mediator code in the knowledge center here.


That’s about all you should ever need to know about the ISAM FIDO2 server endpoints.  It’s now time to get this Node.js application up and running and talking to our ISAM server.


Running the external WebAuthn relying party

Full disclosure – I am not a Node.js application developer. In fact this is my very first Node.js application written after a single day of self-education on the topic. The application is not guaranteed in any way, and may contain security issues due to my own ignorance of Node or shortcuts I have taken for the point of brevity in the demo application. I do hope however it serves as a useful example of how an application may consume ISAM HTTP/JSON services, including FIDO2, SCIM and AAC authentication policies. I picked Node for this application because I wanted to learn a bit about it, and because it is easy to write applications that are brief but quite capable due to the abundance of libraries available.


Obtain the Node.js application from github here: You can do this either as a zip file and extract locally, or with git clone. Either way you’ll end up with a directory containing the following files:

$ find . | sort


There are a couple of different ways we can run the application:

  • Directly in Node.js on the host
  • In Docker, as part of the same docker-compose setup as the rest of ISAM.

Running the Node.js app on the host

If you have Node.js installed, you can run this from the command line.

  • First obtain all the dependencies with:

$ npm install

  • Then update the .env file, specifically to change the RPCONFIGID of the ISAM_FIDO2_ENDPOINT_PREFIX variable. You saw how to get this value earlier.
  • Then run with:

$ npm run start_local

You can check package.json to see what start_local means. Your application should be started and running.


Running the Node.js app in Docker

Alternatively (and preferably for this exercise) to run in docker, first build the container. Using your own docker username substituted below, run:

$ docker build -t <docker_username>/isamfido2rp .

On my system this produces:

$ docker build -t sbweeden/isamfido2rp .

Sending build context to Docker daemon  79.87kB
Step 1/7 : FROM node:10
---> e05cbde47b8f
Step 2/7 : WORKDIR /usr/src/app
---> Running in 4e07f2a9c138
Removing intermediate container 4e07f2a9c138
---> 1dc4a9ac283a
Step 3/7 : COPY package*.json ./
---> 205b25cbdb38
Step 4/7 : RUN npm install
---> Running in 2e3b33066cc8
npm WARN isamfido2rp@0.0.1 No repository field.
added 103 packages from 89 contributors and audited 204 packages in 2.251s
found 0 vulnerabilities
Removing intermediate container 2e3b33066cc8
---> fbecad14a112
Step 5/7 : COPY . .
---> 7f94742783ba
Step 6/7 : EXPOSE 9443
---> Running in 78c91e097c1b
Removing intermediate container 78c91e097c1b
---> ae8c6ea0e2c8
Step 7/7 : CMD [ "node", "server.js" ]
---> Running in 539fdf298ebf
Removing intermediate container 539fdf298ebf
---> 1ebcdfbf8c67
Successfully built 1ebcdfbf8c67
Successfully tagged sbweeden/isamfido2rp:latest


Next change directory to the studentfiles/container-install/compose/iamlab directory where the docker-compose.yaml file exists for the ISAM on docker lab. Update docker-compose.yaml to add the following additional service to the end of the file. Whitespace is very important in a yaml file, so take a close look at the entry for postgresql above, and follow the pattern. Also be sure to update the follow:

  • The image name should use your docker username instead of sbweeden.
  • Update the ISAM_FIDO2_ENDPOINT_PREFIX to include your RPCONFIGID from the relying-party definition.
  • You can change the SECERT to anything you like – this is used for session cookie encryption in Node.js express.


    image: sbweeden/isamfido2rp:latest
    hostname: isamfido2rp
    restart: always
      - SECRET=just_a_demo
      - PORT=3000
      - OAUTH_CLIENT_ID=fido2client
      - OAUTH_CLIENT_SECRET=Passw0rd
      - LOCAL_SSL_SERVER=true
      - LOCAL_SSL_PORT=9443
      - ${WEB1_IP}:9443:9443


Save the docker-compose.yaml file, then either restart, or just bring up the docker-compose services again (the docker-compose down step is optional). Docker compose should just start services that are not already running as needed.

$ docker-compose down

$ docker-compose up -d

Creating network "iamlab_default" with the default driver
Creating iamlab_openldap_1    ... done
Creating iamlab_isamfido2rp_1 ... done
Creating iamlab_isamdsc_1     ... done
Creating iamlab_postgresql_1  ... done
Creating iamlab_isamwrprp1_1  ... done
Creating iamlab_isamruntime_1 ... done
Creating iamlab_isamconfig_1  ... done


You can use this to monitor the log of the new Node.js image (to look for errors or any console.log output):

$ docker logs -f iamlab_isamfido2rp_1

Your SSL app is listening on port 9443


Access the Node.js app with a browser at



From here, login as emily/Passw0rd – the same as what we did in ISAM. The Node.js application uses the ISAM apiauthsvc to validate the username and password against the ISAM user registry using the UsernamePassword AAC authentication policy. Depending on any existing registrations you may have created, you should see a table like this:

Notice the RPID is for the ISAM WebAuthnRP ( Try registering a new authenticator using the Register Authenticator button. On my system I registered with the touchbar again, and now my table shows:


Notice the new entry is for our new RPID, and also there is a Test button. The app has been written to only show the Test button for the RPID that matches the current hostname.

You can experiment with the Test button, along with the advanced registration options. There’s quite a lot to discover in this application.

You can also explore the code of the Node.js application itself to see how API calls are made to ISAM for each of the following operations (see server.js and isamservices.js as a starting point):

  • Username/Password login
  • SCIM (for FIDO2 registration discovery, and for deleting a registration)
  • FIDO2 server endpoints (all four are used)

I would encourage you to add some console.log(...) calls to the Node.js application if you want to log any of the flows to understand more about the data flowing back and forth between the browser, the Node.js application and ISAM.

One particularly important observation about the Node.js application: Because calls to ISAM are made using an administrative service ID (fido2client), it is important that the Node.js app does some validation of request data coming from the browser to ensure that a browser user is not proxying requests as another user. The validateSelf function in isamservices.js assists with this.


That wraps up this rather long blog/tutorial on external programmatic application consumption of ISAM FIDO2 services in the context of WebAuthn. If you have any questions about what was done, or suggestions for improvement (such as a pull-request for my Node.js app), please reach out. Next time I hope to tackle the topic of native Android applications using the FIDO2 APIs on the android platform and how they can interact with ISAM as well. In preparation for that, I would encourage you to take a look at these two Google Code Labs. They will be required reading before tackling Android FIDO2 and ISAM: