Tuesday, June 23, 2020

How to configure Navigation Facet in Adobe Search and Promote?

How to configure Navigation Facet in Adobe Search and Promote?

The Facets can be used to customize your presentation layer and provide your users with a Guided Search that lets them drill down into their search results.
This tutorial explains the approach to configure the facets in Adobe Search & Promote.
The website users can narrow down their search results with enabled facets, there are two types of facets in Search and Promote — Static and Dynamic, we are going to use Static facets for this tutorial.
Refer the below screen shot for example on the facets, the users can narrow down the search based on the brands they interested.



Facet Configuration

The meta data’s can be managed from Settings →Metadata →Definitions

adobe-search-and-promote-facet

adobe-search-and-promote-fccet

The metadata crawled and indexed through URL entry points — URL entry point can be a website URL or feed data through Index Connector, using Index Connector for this tutorial.
The met data is ready now let us configure a facet based on the meta data defined “productType”
Navigate to Design →Navigation →Facets

adobe-search-and-promote-facet

Add a new Facet


adobe-search-and-promote-facet

Facet Name — Select the meta data filed based on which the facet should be defined.
Display Label — Enter display label
Behavior — there are different behaviors like Normal, Category, Category Multi-Select, Sticky and Multi-Select, I am going with Normal behavior for this tutorial
Normal — When a customer clicks a facet, whose behavior is set to Normal, it drills into the search results for that item. From there, the customer can further refine and narrow the number of search results.
Category — Category facets act like navigational elements. These facets are top-level facets that customers typically drill through before revealing facets with attribute options. Category facets do not narrow when other facets are selected and remain open. Clicking a different value within a category facet deselects all other facets on the page except for that category facet’s parents.
Category Multi-Select — facets are category facets that support the selection of multiple items from the facet where the items are “ORed” together.
Sticky — When a customer clicks a facet, whose behavior is set to Sticky, the facet with the selected option remains open during the drill-down. This option is useful when you want to let a customer changes a previous choice.
Multi-Select — Allows the selection of multiple items from a facet, where the items within the facet are “ORed” together. This option is useful for a facet that may show a minor attribute such as colors and you want to let the customer have the ability to build a query that lets them “show shoes in my size that are red or black”.
Show Always — For a normal or sticky facet, sets the facet to remain visible to the customer at all times. This option is only available if you selected Normal, Category, or Sticky from the Behavior drop-down list.
Refer the UI for details on other fields, I am only configuring the Facet Name and Facet Label for this tutorial, enable other fields as required.
Click Add, now the facet is enabled based on the meta data field “Product Type”


adobe-search-and-promote-facet

JSON Transport Template

Created a JSON template(custom_backend_json.tpl) with minimal data to support the search data
The new template can be added by navigating to Designs à Templates à Add Template
Select Template Type as Transport


adobe-search-and-promote-facet

Sample Template Content with minimal configuration– modify based on your requirement, even you can use the existing Transport Template with required customization
<search-content-type-header charset="UTF-8">
{
"general": {
"query" : "<search-query />",
"total" : "<search-total />",
"lower" : "<search-lower />",
"upper" : "<search-upper />"
},
"facets" : [
{
"name" : "productType",
"values" : [<search-field-value-list name="productType" quotes="yes" data="values" sortby="values" encoding="json" />],
"counts" : [<search-field-value-list name="productType" quotes="no" data="results" sortby="values" />]
}
],
"results" : [
<search-results>
{
"fields" :
[
{
"name" : "mdi",
"value" : "<search-display-field name="mdi" length="500" encoding="json" />"
},
{
"name" : "title",
"value" : "<search-display-field name="title" encoding="json" />"
},
{
"name" : "productType",
"value" : "<search-display-field name="productType" encoding="json" />"
}
]
}
<search-if-not-last>,</search-if-not-last>
</search-results>
]
}
Enabled with a single facet — update the template with additional required facets, also add the additional meta data’s and other configurations e.g. breadcrumbs, menus etc
e.g
"facets" : [
        {
            "name" : "productType",
            "values" : [<search-field-value-list name="productType" quotes="yes"  data="values" sortby="values" encoding="json" />],
            "counts" : [<search-field-value-list name="productType" quotes="no"  data="results" sortby="values" />]
        },{"name" : "facet2",
            "values" : [<search-field-value-list name=" facet2" quotes="yes"  data="values" sortby="values" encoding="json" />],
            "counts" : [<search-field-value-list name=" facet2" quotes="no"  data="results" sortby="values" />]
        }
    ],

JSON Presentation Template

Created a JSON template(custom_presentaion_json.tmpl) with minimal data to present the search data to JSON
<guided-content-type-header content="application/json" />
<guided-if-query-param-defined gsname="callback" /><guided-query-param gsname="callback" />(</guided-if-query-param-defined>
 {"general" :{
            "query" : "<guided-query-param gsname='q' />",
            "total" : "<guided-results-total />",
            "page_lower" : "<guided-results-lower>",
            "page_upper" : "<guided-results-upper>",
            "page_total": "<guided-page-total/>" 
        },  
        "facets" :
        [<guided-zone gsname="Facets_Default"><guided-facet  gsname="productType" >{ 
                        "label" : "<guided-facet-display-name gsname="productType" />",
                        "long" : <guided-if-facet-long>true<guided-else-facet-long>false</guided-if-facet-long>,
                        <guided-if-facet-selected>
                        "selected" : true,
                        "undolink" : "<guided-facet-undo-path gsname="productType" />",
                        </guided-if-facet-selected>
                        "values" : 
                        [
                            <guided-facet-values>
                            {
                                "value" : "<guided-facet-value  escape="ijson">",
                                "selected" : "<guided-if-facet-value-selected>true<guided-else-facet-value-selected>false</guided-if-facet-value-selected>",
                                "count" : "<guided-facet-count>",
                                "link" : "<guided-facet-value-path>",
                                "undolink" : "<guided-facet-value-undo-path>",
                                "threshold" : <guided-if-facet-value-equals-length-threshold>true<guided-else-facet-value-equals-length-threshold>false</guided-if-facet-value-equals-length-threshold>
                            }<guided-if-not-last>,</guided-if-not-last>
                            </guided-facet-values>
                        ] 
                    }
                    </guided-facet>
            </guided-zone>
        ],
        "results" :
        [ 
            <guided-results gsname="default">
            {
                "index" : "<guided-result-index />",
                "title" : "<guided-result-field gsname="title" escape="ijson" />",
                "productType" : "<guided-result-field gsname="productType" escape="ijson" />"
            }<guided-if-not-last>,</guided-if-not-last>
            </guided-results>
        ]
    }
<guided-if-query-param-defined gsname="callback">)</guided-if-query-param-defined>

Pre-Search Rule

Define a pre-search rule to configure the presentation and Transport template for the search, the rule is execute when the search request has a parameter “do=json”(the rule can be fired for every search request also)


adobe-search-and-promote-facet
adobe-search-and-promote-facet

The configurations are ready, let us now run a Stage indexing (note the configuration are not pushed to live yet)
Index →Full Index →Staged Index →Run Full Index


adobe-search-and-promote-facet

Access http://stage-xxxxxxxxxxx.guided.ss-omtrdc.net/do=json&sp_staged=1&sp_q=*
xxxxxxxxxxx — Search and Promote account number
This will respond with JSON data which contains the required facet and meta data details.
{
   "general": {
      "query": "",
      "total": "3",
      "page_lower": "1",
      "page_upper": "3",
      "page_total": "1"
   },
   "facets": [
      {
         "label": "productType",
         "long": false,
         "values": [
            {
               "value": "Sample1",
               "selected": "false",
               "count": "1",
               "link": "?do=json;i=1;q1=Sample1;sp_q=*;sp_staged=1;x1=productType",
               "undolink": "",
               "threshold": false
            },
            {
               "value": "Sample2",
               "selected": "false",
               "count": "1",
               "link": "?do=json;i=1;q1=Sample2;sp_q=*;sp_staged=1;x1=productType",
               "undolink": "",
               "threshold": false
            },
            {
               "value": "Sample3",
               "selected": "false",
               "count": "1",
               "link": "?do=json;i=1;q1=Sample3;sp_q=*;sp_staged=1;x1=productType",
               "undolink": "",
               "threshold": false
            }
         ]
      }
   ],
   "results": [
      {
         "index": "",
         "title": "product-title",
         "productType": "Sample1"
      },
      {
         "index": "",
         "title": "product-title",
         "productType": "Sample2"
      },
      {
         "index": "",
         "title": "product-title",
         "productType": "Sample3"
      }
   ]
}
The configuration can be pushed live after successful validation and run a live index →Full Index →Live Index →Run Full Index


adobe-search-and-promote-facet

The URL to access live data http://xxxxxxxxxxx.guided.ss-omtrdc.net/do=json&sp_q=*
The facet data in the response can be used to present the filtering options to users to narrow down the website search.


Sunday, June 21, 2020

Social Login with Google OAuth2— Adobe Experience Manager (AEM)

Social Login with Google OAuth2— Adobe Experience Manager (AEM)


Social login is the ability to present the option for a site visitor to sign in with their social accounts like Facebook, Twitter, LinkedIn and etc. AEM supports OOTB Facebook and Twitter Social logins but Google login is not supported OOTB and need to build custom Provider to support the log in flow for websites.

AEM internally uses the scribejava module to support the Social login flows, scribejava supports multiple providers and both OAuth 1.0 and OAuth 2.0 protocols.

This tutorial explains the steps and the customization required to support the Google login in AEM as Cloud version, the same should work with minimal change for other AEM versions.

Prerequisites

  • Google Account
  • AEM as Cloud Publisher
  • WKND Sample Website
  • Git Terminal
  • Maven
  • Google Login Flow

aem-social-login-with-google


AEM Login URL


http://localhost:4503/j_security_check?configid=google

Auth Page URL


https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s&scope=%s

Access Token URL(POST)


https://oauth2.googleapis.com/token?grant_type=authorization_code&client_id=&client_secret&code=<authorization code received>&redirect_uri=



Steps

  • Create Project in Google Developers Console
  • Setup Custom Google OAuth Provider
  • Configure Service User
  • Configure OAuth Application and Provider
  • Enable OAuth Authentication
  • Test the Login Flow
  • Encapsulated Token Support
  • Sling Distribution user’s synchronization


Create Project in Google Developers Console


As a first step create a new project in google to setup OAuth Client, access https://console.developers.google.com/cloud-resource-manager

Click on “Create Project”

aem-social-login-with-google

Enter a project name and click on Create

aem-social-login-with-google

The project is created now

aem-social-login-with-google

Let us now configure the OAuth client, access settings

aem-social-login-with-google

Search for “API & Services”, click on “APIs & Services”

aem-social-login-with-google

Click on Credentials

aem-social-login-with-google

Click on “Create Credentials” then OAuth client ID

aem-social-login-with-google

The Consent Screen should be configured to initiate the OAuth client id configurations

aem-social-login-with-google

Select User Type as “Internal” or “External” based on your requirement — “Internal” is only available for G-Suite users

aem-social-login-with-google

Enter the application name, Application Logo and Support Email

aem-social-login-with-google

The scopes “email”, “profile” and “openid” are added by default, “profile” scope is enough for basic authentication.

aem-social-login-with-google

Save the configurations now

Now again click on “Create Credentials” → OAuth client ID

aem-social-login-with-google

Select the application type “Web Application” and enter application name

aem-social-login-with-google

“Authorized Javascript origins”— the URL initiate the login, i am going with localhost for demo

“Authorized redirect URI’s” — the URL to be invoked on successful login,http://localhost:4503/callback/j_security_check (use the valid domain for real authentication)

Click on Create button

aem-social-login-with-google

OAuth client is created now, copy the Client ID and Client Secret — these values required to enable the OAuth Authentication handler in AEM.

aem-social-login-with-google

To use the client in production, the OAuth Consent Screen should be submitted for approval.

Click on “Configure Consent Screen” again

aem-social-login-with-google

Enter the required values, “Authorized domains”, “Application Home Page Link”, “Application Privacy Policy Link” and submit for approval

The approval may takes days or weeks, meanwhile the project can be used for development

aem-social-login-with-google


Google Project is ready for use now to test the login in flow

Configure Service User


Enable the service user with required permissions to manage the users in the system, you can use one of the existing service users with required access, I thought of defining new service user(oauth-google-service — name referred in GoogleOAuth2ProviderImpl.java, change the name if required)

Create a system user with name oauth-google-service, navigate to http://localhost:4503/crx/explorer/index.jsp and login as an admin user and click on user administration

aem-social-login-with-google

aem-social-login-with-google

Now enable the required permissions for the user, navigate to http://localhost:4503/useradmin(somehow I am still comfortable with useradmin UI for permission management)

aem-social-login-with-google

Now enable the service user mapping for provider bundle — add an entry into Apache Sling Service User Mapper Service Amendment google.oauth.provider:oauth-google-service=oauth-google-service

aem-social-login-with-google

Setup Custom Google OAuth Provider


As mentioned earlier AEM won’t support Google authentication OOTB, define a new provider to support the authentication with Google.

The custom Google Provider can be downloaded from — https://github.com/techforum-repo/bundles/tree/master/google-oauth-provider

GoogleOAuth2ProviderImpl.java — Provider class to support the Google authentication

GoogleOAuth2Api.java — API class extended from default scribe DefaultApi20 to support Google OAuth 2.0 API integration

GoogleOauth2ServiceImpl.java — Service class to get the Access Token from Google service response

The provider bundle enabled with aem-sdk-api jar for AEM as Cloud Service, the other AEM versions can use the same bundle by changing aem-sdk-api to uber jar.

Clone the repository — git clone https://github.com/techforum-repo/bundles.git

Deploy google-oauth-provider bundle — change the directory to bundles\google-oauth-provider and execute mvn clean install -PautoInstallBundle -Daem.port=4503

Here I am going to enable the authentication for publisher websites, change the port number and deploy to Author if required.

After the successful deployment, you should able to see the Google provider in config manager.

aem-social-login-with-google

The oauth.provider.id can be changed but the same value should b e used while configuring “Adobe Granite OAuth Application and Provider”.

Configure OAuth Application and Provider


Let us now enable the “Adobe Granite OAuth Application and Provider” for Google

Config ID — Enter a unique value, this value should be used while invoking the AEM login URL
Client ID — Copy the Client ID value from Google OAuth Client
Client Secret — Copy the Client Secret value from Google OAuth Client
Scope —”profile”
Provider ID — google
Create users — Select the check box to create AEM users for Google profiles
Callback URL — the same value configured in Google OAuth Client (http://localhost:4503/callback/j_security_check)

aem-social-login-with-google

Enable OAuth Authentication


By default, “Adobe Granite OAuth Authentication Handler” is not enabled by default, the handler can be enabled by opening and saving without doing any changes.



Test the Login Flow


Now the configurations are ready, let us initiate the login — access http://localhost:4503/j_security_check?configid=google from browser(in real scenario you can enable a link or button pointing to this URL). This will take the user to Google Sign-in screen

aem-social-login-with-google

Now you will be logged in to WKND website after successful login from Google Sign in page

aem-social-login-with-google

The user profile is created in AEM

aem-social-login-with-google


aem-social-login-with-google

Whenever the profile data is changed (e.g family_name and given_name) in Google account the same will be reflected to AEM in subsequent login based on the “Apache Jackrabbit Oak Default Sync Handler” configuration.

AEM creates “Apache Jackrabbit Oak Default Sync Handler” configuration specific to each OAuth provider implementations.

The sync handler syncs the user profile data between the external authentication system and AEM repository.

The user profile data is synced based on the User Expiration Time setting, the user data will get synced on the subsequent login after the synced user data expired(default is 1 hr)

Modify the configurations based on the requirement.

aem-social-login-with-google


aem-social-login-with-google


aem-social-login-with-google


Encapsulated Token Support


By default the authentication token is persisted in the repository under user’s profile. That means the authentication mechanism is stateful. Encapsulated Token is the way to configure stateless authentication. It ensures that the cookie can be validated without having to access the repository but the still the user should available in all the publishers for farm configuration.

Refer https://docs.adobe.com/content/help/en/experience-manager-65/administering/security/encapsulated-token.html#StatelessAuthenticationwiththeEncapsulatedToken for more details on Encapsulated Token Support

Enable the Encapsulated Token Support in “Adobe Granite Token Authentication Handler”

aem-social-login-with-google

Sling Distribution user’s synchronization


The users created in a publisher should be synced to all the other publishers in the farm to support the seamless authentication. I am not finding good reference document to explain the user sync in AEM as Cloud(AEM Communities features are not enabled in AEM as Cloud Service, the user sync was enabled through the community components for other AEM version), planning to cover the user sync in another tutorial.

Conclusion


This tutorial is mainly focused on enabling the authenticate the website users through Google account but the same solution can be used with small changes to support different providers. Feel free to give your feed back and changes on the provider bundle.