Customize the Enroll TOTP QR Code

Sometimes you might want to put your own enrollment process into an authentication service flow.
This might be for Just in time enrollment, or some other reason.

I quickly prototyped this out using an infomap, and just want to put it here for reference later.

In short, it uses the easy native functions to detect if a user has one, and if not, generates a temporary secret key that it will only persist if they get right – ie they have saved it in their phone/password manager.

In this sample, I’m generating the QR Code in HTML using an open source library, but you can use your own approach. I’m also using the Local Auth service client – which allows for invocation of another authentication service policy within an infomap context (without a HTTP callout). The policy you’re calling can also be disabled – meaning users can’t initiate the policy from a HTTP endpoint.

importPackage(Packages.com.tivoli.am.fim.trustserver.sts.utilities);
importClass(Packages.com.ibm.security.access.user.UserLookupHelper);
importClass(Packages.com.ibm.security.access.user.User);
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
importClass(Packages.com.tivoli.am.fim.registrations.MechanismRegistrationHelper);
importClass(Packages.com.tivoli.am.fim.authsvc.local.client.AuthSvcClient);

var otppswd = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:parameter", "otppswd");
var username = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:token:attribute", "username");
var errorString = "";
var TOTPIssuer = "ISVA";



if (username == null) {
    //If this is null - then the user hasn't come through expected channels. 
    //Falling back into demo mode. (Remove me if you are taking me live and error out....)
    username = "DemoModeUser";
}

if (MechanismRegistrationHelper.isTotpEnrolled​(username)) {
    errorString = "You've already got a TOTP Enrolled. - You can't TOTP enroll again.";
} else {

    if (otppswd == null) {
        errorString = "Hi " +  username + ". You need to enroll TOTP on your account.";
        //This is likely the first time through
        //Display the default template page here -
        //to challenge for the username and password
        //Generate an TOTP Secret
        var otpSecret = IDMappingExtUtils.generateHmacSecretKey​(40);
        context.set(Scope.SESSION, "urn:ibm:security:asf:demo", "otpSecret", otpSecret);
        page.setValue("/authsvc/otpvalidate.html");
        //Build the QR Code value based on this: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
        macros.put("@OTPSECRETUNENCODED@", "otpauth://totp/"+TOTPIssuer+":"+username+"?secret=" + otpSecret + "&issuer="+ TOTPIssuer);
        success.setValue(false);
    } else {
        //Create a random username to track the 'failed attempts against' - ie don't track them. 
        //These will sit around in the database for the failed attempts period (10mins?) 
        let randomUserName = "ENROLLUSERPREFIX" + (Math.random() + 1).toString(36).substring(2);
        
        //Get the temporary Secret key out of the session
        var otpSecret = context.get(Scope.SESSION, "urn:ibm:security:asf:demo", "otpSecret");
        //Send it to the template again - in case you want to keep it showing?
        macros.put("@OTPSECRETUNENCODED@", "otpauth://totp/Example:someusernamehere?secret=" + otpSecret + "&issuer=Example");
        //Alternatively, send them to another template page? 
        //page.setValue("/authsvc/otpvalidate.html");
        
        //Build the validation payload - sending it to our custom authsvc policy.
        //This policy can be disabled in the LMI to prevent external use - local invocation ignores that. 
        //Note the parameters sending in - username and secretKey. 
        //These are configured as passed in parameters on the mechanism in the policy definition. 
        //The source is "Request", the Attribute Id is "username" and "secretKey", 
        //and the namespace is "urn:ibm:security:asf:request:parameter"
        payload = '{"PolicyId": "urn:ibm:security:authentication:asf:internalTOTPValidation", "username": "' + randomUserName + '", "operation": "verify", "secretKey": "' + otpSecret + '", "otp": "' + otppswd + '"}';
        response = AuthSvcClient.execute(payload);
        
        debugString = "New Mapping: secret:" + otpSecret + " Payload: " + payload + " Response " + response;
        
        response = JSON.parse(response);
        if (response.status == "success") {
            
            if (username != "DemoModeUser") {
                errorString = " ##### Looks like you've got the hang of this. You can now use this mechanism to authenticate your account.";
                
            }else {
                errorString = "##### DemoMode - OTP Valid. DEBUG Info:" + debugString;
            }
        } else {
            errorString = "That doesn't look quite right. Reasons that this might not be working include: Your device might not have the correc time, you're not using a supported mobile app - Download IBM Verify from the Mobile App Store, or you've already used this code before.";
        }

    }
    
}
// This Mechanism can never complete... 
// Probably want to change this if you want to finish and go somewhere or use endPolicyWithoutCredential when done if it's just a USC operation. 
success.setValue(false);
macros.put("@ERROR_MESSAGE@", errorString);

The HTML template is fairly simple, feel free to make it beautiful and suit your own deployment.

<!DOCTYPE html>
<html>
  <body>
    <canvas id="qr"></canvas>
<!-- value: 'otpauth://totp/Example:demouser?secret=D75L7TUXWEBFP7AQK7N5KEYLZBLAQMNY&issuer=Example', -->
    https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js
    <script>
      (function() {
        var qr = new QRious({
  		  foreground: 'blue',
  		  foregroundAlpha: 0.8,
          element: document.getElementById('qr'),
          value: '@OTPSECRETUNENCODED@',
          size: 250
        });
      })();
    </script>
<div>   @ERROR_MESSAGE@ </div>
<div>    Please scan this QR Code, and Enter the TOTP generated below:</div>
<FORM method="POST" action="@ACTION@" autocomplete="off">
                         <INPUT type="password" name="otppswd" class="short" id="otppswd" maxlength="40">
<input type="hidden" name="operation" value="verify">
                        <div class="controls">
                            <INPUT class="submitButton" type="submit" name="Submit" value="Submit">
                        </div>
                    </div>
                </FORM>
  </body>
</html>

Couple of things you will need to do to make this work:

  • Create a new authentication service policy that contains a TOTP Mechanism. You can do that quickly by duplicating the OOTB policy:
  • Give your new policy a name and identifier (this identifier is used in the mapping rule). I used: urn:ibm:security:authentication:asf:internalTOTPValidation

    And then configure the following parameters into the TOTP One Time Password mechanism to ensure that they pick them up dynamically.
  • Disable your policy to prevent external manipulation. (Note – subsequent changes to the policy may re-enable it.)
  • Upload the mapping rule as an infomap type. I called mine otpValidation
  • Upload the HTML Template file. I uploaded mine to /C/authsvc/otpvalidate.html
  • Create a new infomap mechanism – I called mine otpRegisterandValidate
  • Configure the infomap with the mapping rule and template file supplied above. (Note the local /C/ is omitted)
  • Since we’re using a URL in the QR Code, it will be escaped by default by ISVA. To avoid ‘unescaping’, you can configure ISVA to ‘not escape’ the macro under the advanced configuration tab – sps.page.notEscapedMacros.
    The macro name I have used is @OTPSECRETUNENCODED@
  • Finally – create an authentication policy that uses this mechanism. Folks often put an additional authentication step in front of enrollment – so go to town, use any of the mechanisms ISVA has.

    In my example, I just left it on its own.

And hopefully you can run this up.
Navigate to your policy kickoff URL – I’m using short URLs:
https://<hostname>/mga/sps/authsvc/policy/otpvalidate

But if you’re not – you’d use:
https://<hostname>/mga/sps/authsvc?PolicyId=urn:ibm:security:authentication:asf:otpvalidate

I have some basic logic – that says “If you’re unauthenticated” go into demo mode, and you’ll see a message:

If you have an existing authenticated session – in my example a user “appleuser”, you’ll get:

Scan the QR Code with IBM Verify Mobile app (Or your preferred app or password manager)

Enter the code into the site, and you’ll get a positive result if it’s worked:

Or:

Conclusion

There isn’t much you can’t do in an infomap, here is another great example. Hopefully this helps you build the best experience for your customers using ISVA!

Comments are closed.

Website Built with WordPress.com.

Up ↑