From 5ccc8c2ee0a20f8d520b71721b86b1cc3c10934a Mon Sep 17 00:00:00 2001 From: Madhumita Date: Mon, 12 Sep 2022 19:32:11 +0530 Subject: [PATCH] fix: #817 - script for DUO should have the universal prompt, other APIs are deprecated + documentation minor fixes --- .../customization/customize-web-pages.md | 9 + .../scripts/person-authentication.md | 12 +- .../DuoExternalAuthenticator.py | 259 +++++++----------- .../duo-external-authenticator/README.md | 175 +++++++++++- 4 files changed, 285 insertions(+), 170 deletions(-) diff --git a/docs/admin/developer/customization/customize-web-pages.md b/docs/admin/developer/customization/customize-web-pages.md index ffe34ddc518..db2a71c99ea 100644 --- a/docs/admin/developer/customization/customize-web-pages.md +++ b/docs/admin/developer/customization/customize-web-pages.md @@ -55,3 +55,12 @@ Resource bundle names to support other languages should be placed under the same ### Custom image files: 1. All images should be placed under `/opt/gluu/jetty/jans-auth/custom/static/img` 2. Reference it in .xhtml file using the URL `https://your.jans.server/oxauth/ext/resources/img/fileName.png` or `/oxauth/ext/resources/img/fileName.jpg` + +### Page layout, header, footer (xhtml Template) customization + +Templates refers to the common interface layout and style. For example, a same banner, logo in common header and copyright information in footer. + +1. `mkdir -p /opt/jans/jetty/jans-auth/custom/pages/WEB-INF/incl/layout/` +2. Place a modified `template.xhtml` in the above location which will override the [default template file](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/webapp/WEB-INF/incl/layout/template.xhtml) from the war + + diff --git a/docs/admin/developer/scripts/person-authentication.md b/docs/admin/developer/scripts/person-authentication.md index 38ce606fb34..c225d44e251 100644 --- a/docs/admin/developer/scripts/person-authentication.md +++ b/docs/admin/developer/scripts/person-authentication.md @@ -1,6 +1,4 @@ - - -# Adaptive Authentication scripts (Person Authentication scripts) +# Person Authentication scripts The Jans-Auth Server leverages interception scripts of [PersonAuthenticationType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/auth/PersonAuthenticationType.java) which when implemented can facilitate complex multi-step, multi-factor authentication workflows. The authentication flow in the Jans Server is driven by the openID spec. The authorization request to the OP (Jans server) contains an optional query parameter called `acr_values` which is used by the OP to pick an interception script which will be run when `/authorize` endpoint (Authentication flow) is invoked. The name of each script corresponds with its `acr` value in the Jans-Auth Server. Typically, a `PersonAuthenticationType` script can be used to: @@ -58,16 +56,16 @@ All pages are **xhtml** files. The Jans-auth server comes with a default set of This [article](https://github.com/maduvena/jans-docs/wiki/Writing-UI-pages) covers all the details you need to write your own web page. ## Building business logic in Custom script: -Jans-auth server uses Weld 3.0 (JSR-365 aka CDI 2.0) for managed beans. The most important aspects of business logic are implemented through a set of beans. Details and examples of this can be found in this [article](https://jans.io/docs/admin/developer/managed-beans) +Jans-auth server uses Weld 3.0 (JSR-365 aka CDI 2.0) for managed beans. The most important aspects of business logic are implemented through a set of beans. Details and examples of this can be found in this [article](../developer/managed-beans) ## Adding libraries for use in the custom script Java or Python libraries to be imported and used very easily. Remember incase you opt for a python library, it should be written in "pure python" only. -More details of this mentioned [here](https://jans.io/docs/admin/developer/interception-scripts/#using-python-libraries-in-a-script) +More details of this mentioned [here](../interception-scripts/#using-python-libraries-in-a-script) ## Uses of Person Authentication script ### A. Implementing 2FA authentication mechanisms -1. [FIDO2](https://jans.io/docs/admin/developer/scripts/person-authentication-fido2) : Authentications using platform authenticators embedded into a person's device or physical USB, NFC or Bluetooth security keys that are inserted into a USB slot of a computer +1. [FIDO2](/../../../script-catalog/person_authentication/fido2-external-authenticator/README) : Authentications using platform authenticators embedded into a person's device or physical USB, NFC or Bluetooth security keys that are inserted into a USB slot of a computer 2. SMS OTP : 3. Email OTP @@ -75,7 +73,7 @@ More details of this mentioned [here](https://jans.io/docs/admin/developer/inter 1. [Redirect to previous step](https://github.com/JanssenProject/jans/blob/main/jans-linux-setup/jans_setup/static/extension/person_authentication/other/basic.reset_to_step/BasicResetToStepExternalAuthenticator.py): The script here an example of how the number of steps can be varied depending on the context or business requirement. ### C. Implementing Social logins -You can use a `PersonAuthenticationType` script to allow users to sign using credentials from popular **Social Identity providers** or **Inbound Identity Providers** like Facebook, Google and Apple. After users authenticate, thier Social Identity Provider credentials are provisioned into the Jans-auth server. More on this topic in this [article](https://jans.io/docs/admin/recipes/social-login/) +You can use a `PersonAuthenticationType` script to allow users to sign using credentials from popular **Social Identity providers** or **Inbound Identity Providers** like Facebook, Google and Apple. After users authenticate, thier Social Identity Provider credentials are provisioned into the Jans-auth server. More on this topic in this [article](../recipes/social-login/) ### D. Proactively perform fraud detection 1. Impossible travel diff --git a/docs/script-catalog/person_authentication/duo-external-authenticator/DuoExternalAuthenticator.py b/docs/script-catalog/person_authentication/duo-external-authenticator/DuoExternalAuthenticator.py index 262b559f7c0..ef451e92711 100644 --- a/docs/script-catalog/person_authentication/duo-external-authenticator/DuoExternalAuthenticator.py +++ b/docs/script-catalog/person_authentication/duo-external-authenticator/DuoExternalAuthenticator.py @@ -1,76 +1,55 @@ -# Janssen Project software is available under the Apache 2.0 License (2004). See http://www.apache.org/licenses/ for full text. -# Copyright (c) 2020, Janssen Project -# -# Author: Yuriy Movchan -# - from io.jans.service.cdi.util import CdiUtil from io.jans.as.server.security import Identity from io.jans.model.custom.script.type.auth import PersonAuthenticationType -from io.jans.as.server.service import AuthenticationService -from io.jans.as.server.service import UserService -from io.jans.service import MailService -from io.jans.util import ArrayHelper +from io.jans.as.server.service import AuthenticationService, SessionIdService +from io.jans.as.server.service.common import UserService from io.jans.util import StringHelper +from io.jans.util import ArrayHelper from java.util import Arrays +from io.jans.as.server.service.net import HttpService +import os +import java +import sys +from com.duosecurity import Client +from com.duosecurity.exception import DuoException +from com.duosecurity.model import Token +from io.jans.jsf2.service import FacesService +from jakarta.faces.context import FacesContext +from io.jans.jsf2.message import FacesMessages +from io.jans.as.server.util import ServerUtil -import duo_web -import json class PersonAuthentication(PersonAuthenticationType): def __init__(self, currentTimeMillis): self.currentTimeMillis = currentTimeMillis def init(self, customScript, configurationAttributes): - print "Duo. Initialization" - - duo_creds_file = configurationAttributes.get("duo_creds_file").getValue2() - # Load credentials from file - f = open(duo_creds_file, 'r') - try: - creds = json.loads(f.read()) - except: - print "Duo. Initialization. Failed to load creds from file:", duo_creds_file - return False - finally: - f.close() - - self.ikey = str(creds["ikey"]) - self.skey = str(creds["skey"]) - self.akey = str(creds["akey"]) - - self.use_duo_group = False - if (configurationAttributes.containsKey("duo_group")): - self.duo_group = configurationAttributes.get("duo_group").getValue2() - self.use_duo_group = True - print "Duo. Initialization. Using Duo only if user belong to group:", self.duo_group - - self.use_audit_group = False - if (configurationAttributes.containsKey("audit_group")): - self.audit_group = configurationAttributes.get("audit_group").getValue2() - - if (not configurationAttributes.containsKey("audit_group_email")): - print "Duo. Initialization. Property audit_group_email is not specified" - return False - - self.audit_email = configurationAttributes.get("audit_group_email").getValue2() - self.use_audit_group = True - - print "Duo. Initialization. Using audito group:", self.audit_group + print "Duo-Universal. Initialization" + + if (not configurationAttributes.containsKey("client_id")): + print "Duo Universal. Initialization. Property client_id is not specified" + return False + else: + self.client_id = configurationAttributes.get("client_id").getValue2() + + if (not configurationAttributes.containsKey("client_secret")): + print "Duo Universal. Initialization. Property client_secret is not specified" + return False + else: + self.client_secret = configurationAttributes.get("client_secret").getValue2() + + if (not configurationAttributes.containsKey("api_hostname")): + print "Duo Universal. Initialization. Property api_hostname is not specified" + return False + else: + self.api_hostname = configurationAttributes.get("api_hostname").getValue2() - if (self.use_duo_group or self.use_audit_group): - if (not configurationAttributes.containsKey("audit_attribute")): - print "Duo. Initialization. Property audit_attribute is not specified" - return False - else: - self.audit_attribute = configurationAttributes.get("audit_attribute").getValue2() - - print "Duo. Initialized successfully" + print "Duo-Universal. Initialized successfully" return True def destroy(self, configurationAttributes): - print "Duo. Destroy" - print "Duo. Destroyed successfully" + print "Duo-Universal. Destroy" + print "Duo-Universal. Destroyed successfully" return True def getApiVersion(self): @@ -86,19 +65,19 @@ def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes) return None def authenticate(self, configurationAttributes, requestParameters, step): - duo_host = configurationAttributes.get("duo_host").getValue2() - - authenticationService = CdiUtil.bean(AuthenticationService) - + print "Duo-Universal. Authenticate for step %s" % step + identity = CdiUtil.bean(Identity) - if (step == 1): - print "Duo. Authenticate for step 1" + authenticationService = CdiUtil.bean(AuthenticationService) # Check if user authenticated already in another custom script user = authenticationService.getAuthenticatedUser() + if user == None: + print "user is none" credentials = identity.getCredentials() + user_name = credentials.getUsername() user_password = credentials.getPassword() @@ -106,101 +85,81 @@ def authenticate(self, configurationAttributes, requestParameters, step): if (StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password)): userService = CdiUtil.bean(UserService) logged_in = authenticationService.authenticate(user_name, user_password) - + if (not logged_in): + print "return false" return False - - user = authenticationService.getAuthenticatedUser() - - if (self.use_duo_group): - print "Duo. Authenticate for step 1. Checking if user belong to Duo group" - is_member_duo_group = self.isUserMemberOfGroup(user, self.audit_attribute, self.duo_group) - if (is_member_duo_group): - print "Duo. Authenticate for step 1. User '" + user.getUserId() + "' member of Duo group" - duo_count_login_steps = 2 - else: - self.processAuditGroup(user) - duo_count_login_steps = 1 - - identity.setWorkingParameter("duo_count_login_steps", duo_count_login_steps) - + identity.setWorkingParameter('username',user_name) return True + elif (step == 2): - print "Duo. Authenticate for step 2" - user = authenticationService.getAuthenticatedUser() - if user == None: - print "Duo. Authenticate for step 2. Failed to determine user name" - return False - - user_name = user.getUserId() - - sig_response_array = requestParameters.get("sig_response") - if ArrayHelper.isEmpty(sig_response_array): - print "Duo. Authenticate for step 2. sig_response is empty" - return False - - duo_sig_response = sig_response_array[0] - - print "Duo. Authenticate for step 2. duo_sig_response: " + duo_sig_response - - authenticated_username = duo_web.verify_response(self.ikey, self.skey, self.akey, duo_sig_response) - - print "Duo. Authenticate for step 2. authenticated_username: " + authenticated_username + ", expected user_name: " + user_name - - if (not StringHelper.equals(user_name, authenticated_username)): - return False - - self.processAuditGroup(user) - - return True + + identity = CdiUtil.bean(Identity) + + state = ServerUtil.getFirstValue(requestParameters, "state") + # Get state to verify consistency and originality + if identity.getWorkingParameter('state_duo') == state : + + # Get authorization token to trade for 2FA + duoCode = ServerUtil.getFirstValue(requestParameters, "duo_code") + try: + token = self.duo_client.exchangeAuthorizationCodeFor2FAResult(duoCode, identity.getWorkingParameter('username')) + print "token status %s " % token.getAuth_result().getStatus() + except: + # Handle authentication failure. + print "authentication failure", sys.exc_info()[1] + return False + + # User successfully passed Duo authentication. + + if "allow" == token.getAuth_result().getStatus(): + return True + + return False + else: + print "Neither step 1 or 2" return False def prepareForStep(self, configurationAttributes, requestParameters, step): - identity = CdiUtil.bean(Identity) - authenticationService = CdiUtil.bean(AuthenticationService) - - duo_host = configurationAttributes.get("duo_host").getValue2() - + print "Duo-Universal. Prepare for step %s" % step + if (step == 1): - print "Duo. Prepare for step 1" - return True elif (step == 2): - print "Duo. Prepare for step 2" - - user = authenticationService.getAuthenticatedUser() - if (user == None): - print "Duo. Prepare for step 2. Failed to determine user name" - return False - user_name = user.getUserId() - - duo_sig_request = duo_web.sign_request(self.ikey, self.skey, self.akey, user_name) - print "Duo. Prepare for step 2. duo_sig_request: " + duo_sig_request - - identity.setWorkingParameter("duo_host", duo_host) - identity.setWorkingParameter("duo_sig_request", duo_sig_request) + identity = CdiUtil.bean(Identity) + user_name = identity.getWorkingParameter('username') + facesContext = CdiUtil.bean(FacesContext) + request = facesContext.getExternalContext().getRequest() + httpService = CdiUtil.bean(HttpService) + url = httpService.constructServerUrl(request) + "/postlogin.htm" + + try: + self.duo_client = Client(self.client_id,self.client_secret,self.api_hostname,url) + self.duo_client.healthCheck() + except: + print "Duo-Universal. Duo config error. Verify the values in Duo-Universal.conf are correct ", sys.exc_info()[1] + + state = self.duo_client.generateState() + identity.setWorkingParameter("state_duo",state) + prompt_uri = self.duo_client.createAuthUrl(user_name, state) + + facesService = CdiUtil.bean(FacesService) + facesService.redirectToExternalURL(prompt_uri ) - return True + return True + else: return False def getExtraParametersForStep(self, configurationAttributes, step): - if step == 2: - return Arrays.asList("duo_count_login_steps", "cas2_user_uid") - - return None + return Arrays.asList("state_duo", "username") def getCountAuthenticationSteps(self, configurationAttributes): - identity = CdiUtil.bean(Identity) - if (identity.isSetWorkingParameter("duo_count_login_steps")): - return int(identity.getWorkingParameter("duo_count_login_steps")) - return 2 def getPageForStep(self, configurationAttributes, step): - if (step == 2): - return "/auth/duo/duologin.xhtml" + print "Duo-Universal. getPageForStep - %s " % step return "" def getNextStep(self, configurationAttributes, requestParameters, step): @@ -212,28 +171,4 @@ def getLogoutExternalUrl(self, configurationAttributes, requestParameters): def logout(self, configurationAttributes, requestParameters): return True - - def isUserMemberOfGroup(self, user, attribute, group): - is_member = False - member_of_list = user.getAttributeValues(attribute) - if (member_of_list != None): - for member_of in member_of_list: - if StringHelper.equalsIgnoreCase(group, member_of) or member_of.endswith(group): - is_member = True - break - - return is_member - - def processAuditGroup(self, user): - if (self.use_audit_group): - is_member = self.isUserMemberOfGroup(user, self.audit_attribute, self.audit_group) - if (is_member): - print "Duo. Authenticate for processAuditGroup. User '" + user.getUserId() + "' member of audit group" - print "Duo. Authenticate for processAuditGroup. Sending e-mail about user '" + user.getUserId() + "' login to", self.audit_email - - # Send e-mail to administrator - user_id = user.getUserId() - mailService = CdiUtil.bean(MailService) - subject = "User log in: " + user_id - body = "User log in: " + user_id - mailService.sendMail(self.audit_email, subject, body) + \ No newline at end of file diff --git a/docs/script-catalog/person_authentication/duo-external-authenticator/README.md b/docs/script-catalog/person_authentication/duo-external-authenticator/README.md index e2f2d5e696f..de360d8255e 100644 --- a/docs/script-catalog/person_authentication/duo-external-authenticator/README.md +++ b/docs/script-catalog/person_authentication/duo-external-authenticator/README.md @@ -1 +1,174 @@ -This is a placeholder \ No newline at end of file + + +## Integrating DUO's Universal Prompt as an authentication method in Janssen server + +[Duo Security](https://duosecurity.com) is a SaaS authentication provider. This document will explain how to use Janssen's [Duo interception script](https://github.com/JanssenProject/jans/blob/main/jans-linux-setup/jans_setup/static/extension/person_authentication/DuoExternalAuthenticator.py) to configure the Janssen Server for a two-step authentication process with username and password as the first step, and Duo as the second step. The script invokes the Universal Prompt which is a redesign of Duo’s traditional authentication prompt. + +## Authentication flow +``` +mermaid +sequenceDiagram + title Integrating DUO's Universal Prompt as an authentication method in Janssen server + autonumber 1 + Jans AS<-User agent: Invoke /authorize endpoint + Jans AS->User agent: Present Username password screen + User agent->Jans AS: Submit credentials + Jans AS->DUO Security Service: Redirects login request to IDP + + loop n times - (multistep authentication) + DUO Security Service->User agent: Present universal prompt screen + User agent->DUO Security Service: Present credentials + end + + DUO Security Service->Jans AS: return state, code(duo_code) + Jans AS->Jans AS: check state + Jans AS->DUO Security Service: send code (duo_code) in exchange for token + DUO Security Service -> Jans AS: token + Jans AS->Jans AS: inspect token if (token.getAuth_result().getStatus() == success), \nmark user as authenticated + + opt if new user + Jans AS->Jans AS: 9. Dynamic enrollment or registration + end + + Jans AS->User agent: 10. write Jans session cookie +``` + +## Prerequisites +- A Janssen Server +- [Duo interception script](https://github.com/JanssenProject/jans/blob/main/jans-linux-setup/jans_setup/static/extension/person_authentication/DuoExternalAuthenticator.py) (included in the default Janssen Server distribution); +- An account with [Duo Security](https://duo.com/). +- Users should download the DUO mobile app + +## Configure Duo Account + +1. [Sign up](https://duo.com/) for a Duo account. + +2. Log in to the Duo Admin Panel and navigate to Applications. + +3. Click Protect an Application and locate Web SDK in the applications list. Click Protect this Application to get your client ID, secret key, and API hostname. + +For additional info for the steps refer to Duo's Web SDK 4, check [this article](https://duo.com/docs/duoweb-v4). + +## Configure Jans-auth server (AS) +### 1. Add the duo-universal Dependency to your jans-auth server +The dependencies have to be added separately as mentioned in the steps below. Using a fat jar (duo-universal-sdk-1.0.3-with-dependencies.jar leads to conflicts.) + + - [ ] Copy these jar files to the following jans-auth folder inside the Janssen Server chroot: `/opt/jans/jetty/jans-auth/custom/libs` +
Dependency jars: +
* [duo-universal-sdk-1.0.3.jar](https://repo1.maven.org/maven2/com/duosecurity/duo-universal-sdk/1.0.3/duo-universal-sdk-1.0.3.jar) , +
* [converter-jackson-2.1.0.jar](https://repo1.maven.org/maven2/com/squareup/retrofit2/converter-jackson/2.1.0/converter-jackson-2.1.0.jar) , +
* [java-jwt-3.3.0.jar](https://repo1.maven.org/maven2/com/auth0/java-jwt/3.3.0/java-jwt-3.3.0.jar), +
* [logging-interceptor-3.3.1.jar](https://repo1.maven.org/maven2/com/squareup/okhttp3/logging-interceptor/3.3.1/logging-interceptor-3.3.1.jar), +
* [lombok-1.18.16.jar](https://repo1.maven.org/maven2/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar), +
* [retrofit-2.5.0.jar](https://repo1.maven.org/maven2/com/squareup/retrofit2/retrofit/2.5.0/retrofit-2.5.0.jar), +
* [okio-2.9.0.jar](https://repo1.maven.org/maven2/com/squareup/okio/okio/2.9.0/okio-2.9.0.jar), +
* [okhttp-3.12.0.jar](https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.12.0/okhttp-3.12.0.jar), +
* [kotlin-stdlib-1.4.21.jar](https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.4.21/kotlin-stdlib-1.4.21.jar) + - [ ] Edit /opt/jans/jetty/jans-auth/webapps/jans-auth.xml and add the following line: +`./custom/libs/duo-universal-sdk-1.0.3.jar,./custom/libs/converter-jackson-2.1.0.jar,./custom/libs/java-jwt-3.3.0.jar,./custom/libs/logging-interceptor-3.3.1.jar,./custom/libs/lombok-1.18.16.jar,./custom/libs/retrofit-2.5.0.jar,./custom/libs/okio-2.9.0.jar,./custom/libs/okhttp-3.12.0.jar,./custom/libs/kotlin-stdlib-1.4.21.jar` + - [ ] Restart the `jans-auth` service + + +### 2. Add custom script + +1. Create `cs.json` with the contents of a CUSTOM script. To do that, run the following command. +``` +/opt/jans/jans-cli/config-cli.py --schema /components/schemas/CustomScript > /tmp/cs.json +``` +2. Edit the file's contents to reflect the addition of the duo custom script. + * Set enabled flag `true` + * Configure the api_hostname, client_id and client_secret + | Property |Status | Description | Example | + |-----------------------|---------------|-----------------------|-----------------------| + |api_hostname |Mandatory |URL of the Duo API Server|api-random.duosecurity.com| + |client_id |Mandatory |Value from the Duo application using Web SDK 4 that was registered using DUO Admin console|DI3ICTTJKLL8PPPNGH7YI| + |client_secret |Mandatory|Value from the Duo application using Web SDK 4 that was registered using DUO Admin console|eEbJdi3hg42zxyFYbHArU5RuioPP| + + * `name` field should reflect the use case + * `script_type` should be `PERSON_AUTHENTICATION` + +``` + +{ + "dn": null, + "inum": null, + "name": "duo", + "aliases": [], + "description": "DUO's custom script", + "script": "_file /root/script.py", + "scriptType": "PERSON_AUTHENTICATION", + "programmingLanguage": "JYTHON", + "moduleProperties": { + "value1": null, + "value2": null, + "description": null + }, + "configurationProperties": + +[{ + "value1": "api_hostname", + "value2": "api-random.duosecurity.com", + "description": "URL of the Duo API Server", + "hide": true + }, +{ + "value1": "client_id", + "value2": "DI3ICTTJKLL8PPPNGH7YI", + "description": "Value from the Duo application using Web SDK 4 that was registered using DUO Admin console", + "hide": true + }, + { + "value1": "client_secret", + "value2": "eEbJdi3hg42zxyFYbHArU5RuioPP", + "description": "Value from the Duo application using Web SDK 4 that was registered using DUO Admin console", + "hide": true + } +] +, + "level": "integer", + "revision": 0, + "enabled": true, + "scriptError": { + "raisedAt": null, + "stackTrace": null + }, + "modified": false, + "internal": false +} +``` +3. Add the custom script +``` +/opt/jans/jans-cli/config-cli.py --operation-id post-config-scripts --data /tmp/cs.json +``` + +Now Duo is an available authentication mechanism for your Janssen Server. This means that, using OpenID Connect `acr_values`, applications can now request Duo authentication for users. + +!!! Note + To make sure Duo has been enabled successfully, you can check your Janssen Server's OpenID Connect configuration by navigating to the following URL: `https:///.well-known/openid-configuration`. Find `"acr_values_supported":` and you should see `"duo"`. + +## Make Duo the Default Authentication Mechanism +For CURL commands, use this [link](https://github.com/JanssenProject/jans/blob/main/docs/admin/config-guide/curl.md#2-enable-an-authentication-script) as a reference. + +Steps: +1. Create a file say `duo-auth-default.json` with the following contents +``` +{ + "defaultAcr": "duo" +} +``` +2.Update the default authentication method to Google Sign-in +``` +/opt/jans/jans-cli/config-cli.py --operation-id put-acrs --data /tmp/duo-auth-default.json +``` +:memo: **NOTE** + +To make sure `duo` has been enabled successfully as a default authentication method, you can check your Janssen Server's OpenID Connect configuration by navigating to the following URL: `https:///.well-known/openid-configuration`. Find `"acr_values_supported":` and you should see `"duo"`. + +## Test the feature +To test , enter the complete URL for authorization in a browser or create a simple web page with a link that simulates the user sign-in attempt. If the server is configured properly, the first page for the selected authentication method will be displayed to the user. + +An example of a complete URL looks like this - +``` +https:///jans-auth/authorize.htm?response_type=code&redirect_uri=https:///admin&client_id=&scope=openid+profile+email+user_name&state=faad2cdjfdddjfkdf&nonce=dajdffdfsdcfff +``` +