Leverage contextual information to implement intelligent authentication workflows in the Gluu Server.
Two-factor authentication (2FA) is proven to increase account security, but it also adds friction to the user experience.
Frequently 2FA is best employed only when there’s a reasonable likelihood of fraud–for example, if the user’s device or IP address is unrecognized.
To implement custom policies and logic for authentication in the Gluu Server, you can use person authentication scripts. In fact, this specific policy, i.e. checking for unrecognized devices and locations, is supported OOTB in our self-service 2FA product, Casa.
But this type of policy can be implemented in any authentication script. And in this short tutorial, we’ll dissect the Casa interception script to show how you can apply similar policies in your own scripts.
Device details
The default Gluu login page template uses the platform detection library, bestie.js, to gather operating system and browser details (i.e. device details) for each user authenticating at the service:
1 2 3 4 | <script src=“#{oxAuthConfigurationService.getJsLocation()}/platform.js” /> |
In the script, the user’s device data is grabbed and stored in a hidden form field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function fillPlatformField() { try { re = /^([^\.]+\.[^\.]+)\..+/; result = re.exec(platform.version); if (result != null) { platform.version=result[1]; } document.getElementById(“loginForm:platform”).value = JSON.stringify(platform); } catch (e) { } } |
Which is then parsed by the interception script:
1 2 3 4 5 | platform_data = self.parsePlatformData(requestParameters) |
Now we have the user’s device information.
Geo-location details
To get the user’s geo-location, we call the IP-API service in the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def getGeolocation(self, identity): session_attributes = identity.getSessionId().getSessionAttributes() if session_attributes.containsKey(“remote_ip”): remote_ip = session_attributes.get(“remote_ip”) if StringHelper.isNotEmpty(remote_ip): httpService = CdiUtil.bean(HttpService) http_client = httpService.getHttpsClient() http_client_params = http_client.getParams() http_client_params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 4 * 1000) geolocation_service_url = “http://ip-api.com/json/%s?fields=country,city,status,message“ % remote_ip geolocation_service_headers = { “Accept” : “application/json” } |
And now we have the user’s country and city available in the server for further processing..
Enforcing 2FA policies
Now we can use the above data to enforce 2FA policies.
In the Casa script, you can see the device and location-related 2FA policies here and here, respectively:
1 2 3 | def determineSkip2FA(self, userService, identity, foundUser, deviceInf): |
1 2 3 | def process2FAPolicy(self, identity, foundUser, deviceInf, locationCriterion, deviceCriterion): |
Upon a successful login, the newly trusted device and/or location data is added to the attribute oxTrustedDevicesInfo
:
1 2 3 4 5 6 7 8 9 10 11 | #Update the list of trusted devices if 2fa passed if success: print “Casa. authenticate. 2FA authentication was successful” tdi = session_attributes.get(“trustedDevicesInfo”) if tdi == None: print “Casa. authenticate. List of user’s trusted devices was not updated” else: user.setAttribute(“oxTrustedDevicesInfo”, tdi) userService.updateUser(user) else: print “Casa. authenticate. 2FA authentication failed” |
So now, if the policy is to only enforce 2FA when an unrecognized device or location is detected, the user won’t be prompted again when authenticating from the same device and/or location.
And that’s it!
Put this strategy to use in one of your authentication scripts to increase security without negatively impacting user experiences.