Saturday, June 13, 2020

Sling Content Distribution in AEM (Part 2) — Reverse Distribution

This tutorial is the continuation of earlier tutorial on Sling Content Distribution in AEM, refer the following URL for part1 tutorial - https://www.albinsblog.com/2020/05/sling-content-distribution-sync-use.html


In this tutorial let us see the details on Sling Reverse Distribution on AEM.

REVERSE DISTRIBUTION - DEFINITION

  • A reverse distribution setup allows one to transfer content from a farm of source instances(publisher) to a target instance(author). 
  • That is done by pulling the content from source instances(publisher) into the target instance(author).

sling-reverse-distribution
This will help us to sync the data generated in farms of publishers into the Author instances.

REVERSE DISTRIBUTION - CONFIGURATIONS

sling-reverse-distribution

  • configure a “queue” agent and package exporter on publisher(source instance)
org.apache.sling.distribution.agent.impl.QueueDistributionAgentFactory-reverse.json            
    name="reverse"

org.apache.sling.distribution.packaging.impl.exporter.AgentDistributionPackageExporterFactory-reverse
    name="reverse"
    agent.target="(name=reverse)"
  • configure a “reverse" agent on author(target instance) pointing to the URL of the exporter on publish, multiple publisher endpoints can be configured
org.apache.sling.distribution.agent.impl.ReverseDistributionAgentFactory-reverse.json            
    name="reverse"
    packageExporter.endpoints=["http://localhost:4503/libs/sling/distribution/services/exporters/reverse"]

REVERSE DISTRIBUTION - DEMO

  • Configure Reverse Agent in Author
  • Configure Queue agent  and exporter on Publisher
  • Enable Triggers – Scheduled/JCREvent
  • Test – CURL/Triggers

Configure Reverse Agent in Author


Configure a Reverse Agent in Author that will PULL distribution content from publishers endpoints based on the configuration.

Access http://localhost:4502/aem/start.html, Tools - Deployments - Distribution

sling-content-distribution-aem



Create new Distribution agent of type - Reverse Distribution 

Enter a name - "reverse"
Title - "reverse"
Check "Enabled"
Service Name - Service name is optional, if required create a service user with required permission
Change the lo level if required
Add exporter endpoint URL's - the URL point to the publisher, multiple endpoint URL's can be configured, http://localhost:4503/libs/sling/distribution/services/exporters/reverse(reverse is the Queue Distribution agent name of publisher)

distribution-agent-aem


distribution-agent-aem



Save the configurations , the agent is created now but the status is paused 

distribution-agent-aem


Resume the agent by clicking on the resume button on the agent detail page.

distribution-agent-aem


distribution-agent-aem

The agent is now ready to Pull the distribution content from publisher.

Configure Queue agent and exporter on Publisher

Configure a queue agent that places the changes into the queues and an exporter that exports packages from the queue agent.

Access http://localhost:4503/aem/start.html, Tools - Deployments - Distribution

Create new Distribution agent of type - Queue
Enter a name - "reverse"
Title - "reverse"
Check "Enabled"
Service Name - Service name is optional, if required create a service user with required permission
Change the lo level if required
Allowed Roots - Add the root paths the agent is responsible for distribution e.g /content/we-retail(if required multiple root paths can be configured )

distribution-agent-aem


Save the configurations, Queue Distribution Agent is enabled now

distribution-agent-aem


Let us now configure Exporter


Enter name - "reverse"
The target reference for the DistributionAgent that will be used to export packages - "(name=reverse)", here "reverse" is the queue agent name configured in the previous step

distribution-agent-aem


Now the initial configurations are ready, let us test the reverse distribution scenario through curl commands

Modify some content under /content/we-retail node in publisher

distribution-agent-aem.

Execute the below curl commands

curl -u admin:admin http://localhost:4503/libs/sling/distribution/services/agents/reverse -d "action=ADD" -d "path=/content/we-retail/jcr:content"   (add the modified content to publisher Distribution queue)

distribution-agent-aem

Now the content is queued to the publisher distribution queue

distribution-agent-aem


curl -u admin:admin http://localhost:4502/libs/sling/distribution/services/agents/reverse -d "action=PULL" (PULL the content from publisher queue to author)

distribution-agent-aem

 Now the content is pulled to author

distribution-agent-aem

Let us now see how to automate the reverse distribution through triggers

Configure a JCR Event Trigger in Publisher


Configure a JCR Event Trigger in Publisher to add the JCR changes under the configured path to the Distribution queue

Access http://localhost:4503/system/console/configMgr/org.apache.sling.distribution.trigger.impl.JcrEventDistributionTriggerFactory

Enter name - "reverse-sync"
Path for which the changes are distributed - "/content/we-retail"
Service Name - Enter the service name with required access, i am using the default one for demo(socialpubsync-distributionService), the trigger will not be activated without configuring the service user
Use deep distribution - Enable this if want to distribute the subtree of the configured node on any events

distribution-agent-aem

Now link the trigger to the "Apache Sling Distribution Agent - Queue Agents Factory"  configured with the name "reverse" in the earlier step, Triggers - (name=reverse-sync)

distribution-agent-aem

Configure a Scheduled Event Trigger in Author

Configure a Scheduled Event Trigger in Author to pull the content from publishers Distribution Queue


Enter name - "reverse-sync"
Distribution Type - "PULL"
Distributed Path, the path to be distributed periodically- "/content/we-retail"
Service Name - Enter the service name with required access, i am using the default one for demo(socialpubsync-distributionService),  the trigger will not be activated without configuring the service user
Interval in Seconds - he number of seconds between distribution requests. Default 30 seconds

distribution-agent-aem


Now link the trigger to the "Apache Sling Distribution Agent - Reverse Agents Factory"  configured with the name "reverse" in the earlier step, Triggers - (name=reverse-sync)

distribution-agent-aem


Now the content modification from publisher under /content/we-retail node will be synced to author on every 30 seconds


This concludes the reverse distribution configuration between publisher and author, the content changes from publisher is pulled to author. We can configure multiple publisher endpoints in the Author reverse distribution agent to pull the content changes . The triggers can be configured in Author and Publishers to completely automate the reverse distribution of the contents. Let us continue with distribution sync in next tutorial.


Thursday, June 11, 2020

CRXDE and Package manager not accessible in AEM publisher - com.day.crx.delite.impl.servlets.InitServlet Error while retrieving infos

CRXDE and Package manager not accessible in AEM publisher - com.day.crx.delite.impl.servlets.InitServlet Error while retrieving infos


I was facing an issue while accessing the CRXDE and Package manager on AEM publisher, the pages displayed as blank and keep loading

aem-crxde-package-manager-not-loading

The response to the "http://localhost:4503/crx/de/init.jsp?_dc=1591923460206"  service was 500

aem-crxde-package-manager-not-loading

The below exception was seen in the error log file

11.06.2020 19:49:20.558 *ERROR* [qtp1936302775-72] com.day.crx.delite.impl.servlets.InitServlet Error while retrieving infos
java.lang.NullPointerException: null
at com.day.crx.delite.impl.servlets.InitServlet.getFormattedName(InitServlet.java:126) [com.adobe.granite.crxde-lite:1.1.42]
at com.day.crx.delite.impl.servlets.InitServlet.doService(InitServlet.java:84) [com.adobe.granite.crxde-lite:1.1.42]
at com.day.crx.delite.impl.AbstractServlet.service(AbstractServlet.java:52) [com.adobe.granite.crxde-lite:1.1.42]
at com.day.crx.delite.impl.MainServlet.doService(MainServlet.java:132) [com.adobe.granite.crxde-lite:1.1.42]
at com.day.crx.delite.impl.MainServlet.service(MainServlet.java:109) [com.adobe.granite.crxde-lite:1.1.42]
at org.apache.felix.http.base.internal.handler.ServletHandler.handle(ServletHandler.java:123) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.InvocationChain.doFilter(InvocationChain.java:86) [org.apache.felix.http.jetty:4.0.8]
at com.adobe.granite.license.impl.LicenseCheckFilter.doFilter(LicenseCheckFilter.java:308) [com.adobe.granite.license:1.2.10]
at org.apache.felix.http.base.internal.handler.FilterHandler.handle(FilterHandler.java:142) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.InvocationChain.doFilter(InvocationChain.java:81) [org.apache.felix.http.jetty:4.0.8]
at org.apache.sling.i18n.impl.I18NFilter.doFilter(I18NFilter.java:131) [org.apache.sling.i18n:2.5.14]
at org.apache.felix.http.base.internal.handler.FilterHandler.handle(FilterHandler.java:142) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.InvocationChain.doFilter(InvocationChain.java:81) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.Dispatcher$1.doFilter(Dispatcher.java:146) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.whiteboard.WhiteboardManager$2.doFilter(WhiteboardManager.java:1002) [org.apache.felix.http.jetty:4.0.8]
at org.apache.sling.security.impl.ReferrerFilter.doFilter(ReferrerFilter.java:326) [org.apache.sling.security:1.1.16]
at org.apache.felix.http.base.internal.handler.PreprocessorHandler.handle(PreprocessorHandler.java:136) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.whiteboard.WhiteboardManager$2.doFilter(WhiteboardManager.java:1008) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.sslfilter.internal.SslFilter.doFilter(SslFilter.java:97) [org.apache.felix.http.sslfilter:1.2.6]
at org.apache.felix.http.base.internal.handler.PreprocessorHandler.handle(PreprocessorHandler.java:136) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.whiteboard.WhiteboardManager$2.doFilter(WhiteboardManager.java:1008) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.whiteboard.WhiteboardManager.invokePreprocessors(WhiteboardManager.java:1012) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.Dispatcher.dispatch(Dispatcher.java:91) [org.apache.felix.http.jetty:4.0.8]
at org.apache.felix.http.base.internal.dispatch.DispatcherServlet.service(DispatcherServlet.java:49) [org.apache.felix.http.jetty:4.0.8]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725) [org.apache.felix.http.servlet-api:1.1.2]
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:873) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:542) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1701) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1345) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1668) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:220) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.Server.handle(Server.java:502) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:370) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:267) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765) [org.apache.felix.http.jetty:4.0.8]
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683) [org.apache.felix.http.jetty:4.0.8]
at java.base/java.lang.Thread.run(Thread.java:834)

The CRXDE/Package Manager was accessible by login through crx/explorer(http://localhost:4503/crx/explorer/index.jsp) or through system/console(http://localhost:4503/system/console) but not directly.

After analysis, identified the issue can happen in one of the below scenarios
  • Read access for the anonymous user to read own user node has removed
  • Anonymous user deleted by mistake
The actual root cause for our issue was first scenario -  Read access for the anonymous user to read own user node has removed, the anonymous user did not have the permission to access own user node - by default AEM enables the read permission to the anonymous user to access its own user node.

The issue was caused after accidentally removing the read access to anonymous user to read own user node.

aem-crxde-package-manager-not-loading

aem-crxde-package-manager-not-loading


The issue got resolved after re-enabling the read access for anonymous user to own user node

aem-crxde-package-manager-not-loading


aem-crxde-package-manager-not-loading


Another option to resolve the issue is deleting the anonymous user(or deleted accidentally ) and restarting the server - the anonymous user recreated upon restart but unfortunately AEM not enabling the read access for the anonymous user to own user node on recreation - the permission was enabled only during the initial server setup.

The  problem should be addressed by enabling the read permission manually.


Sunday, June 7, 2020

How to enable Google CDN for custom origin websites | Google CDN for external websites

How to enable Google CDN for custom origin websites | Google CDN for external websites


Introduction


Cloud CDN by Google is a low-latency content delivery solution for small to enterprise business. Cloud CDN (Content Delivery Network) uses Google's globally distributed edge points of presence to cache external HTTP(S) load balanced content close to your users, when a user goes to your website, they retrieve your content from the nearest CDN location rather than from your web server. Caching content at the edges of Google's network provides faster delivery of content to your users while reducing serving costs.

Some of the features of Google Cloud CDN
  • HTTP/2 support
  • Global distribution with anycast IP
  • Integrated with Google Cloud – Cloud Monitoring and Cloud Logging
  • Purge cache instantly
This tutorial explains the steps required to enable the Google CDN in front of the custom origin websites running outside of Google Cloud platform.

Prerequisite

  • Google Cloud Account
  • Externally accessible websites


Scenario

I have a website running in external server – test.albinsblog.com, I want to activate the CDN in front of my website.

google-cdn-custom-origin


Steps

  • Register for a Google Cloud Account
  • Create new HTTP(S) Load Balancer
  • Enable CDN
  • Enable A-Record for DNS


Register for a Google Cloud CDN


If you not enabled for Google Cloud CDN then access https://cloud.google.com/cdn and click on “Try Cloud CDN Free”

google-cloud-cdn-setup

google-cloud-cdn-setup

Configure the Payment Profile. This will take sometime to complete the configuration; you should be seeing “Compute Engine is getting Ready”, once after the Compute Engine is ready you will see the option to add CDN Origin.

google-cloud-cdn-setup


Create new HTTP(S) Load Balancer


As a first step, let us configure the new HTTP(S) Load Balancer. Click Add origin on the previous screen and Click on Continue on the Next screen. Select “Use a custom origin” and click on “Create a load balancer.

google-cloud-cdn-setup

Enter a unique name for the load balancer. Click on back end configuration and create a backend service

google-cloud-cdn-setup-backend


Enter a name and select backend Type as “Internet Network endpoint group”, change the protocol as required – this protocol will be used by Google Load balancer to connect to the origin server. I am going with HTTP protocol for the demo

Select “Create Internet network endpoint group” from "Internet network endpoint group" dropdown

google-cloud-cdn-setup-backend

Enter a name for Internet network endpoint group, select “Network endpoint group type” as Network endpoint group (Internet).

Add the default port to connect to origin server – my case the server is accessible through port 80, enter the IP address or the Fully qualified name of the origin server – I am configuring the IP address

Click on Create
google-cloud-cdn-setup-network-endpoint

Select the created Internet network endpoint group in backend service configuration screen and click on Done

Select "Enable Cloud CDN" and click on create

google-cloud-cdn-setup-backend

Select Host and Path Rules and enable the require rules – I am going with default rule configurations 

Now click on Frontend Configurations


google-cloud-cdn-host-path-rules

Enter a name

Select the protocol users uses to connect to the website, the SSL certificate should be uploaded if the protocol is selected as HTTPS with supported Domains.

google-cloud-cdn-frontend-configuration

You can upload the existing certificate or create google managed SSL certificate

I am going with HTTP for the demo

google-cloud-cdn-ssl

Create new IP address – the default IP address lives for short time, create a static IP address.

google-cloud-cdn-static-ip-address

google-cloud-cdn-static-ip-address

Review the configurations and click on Create

google-cdn-loadbalancer-configurations


Now the HTTP(S) Load Configuration is ready and enabled with CDN

Enable A-Record for DNS


Now your website is accessible through the Google CDN Front end IP, add a A-Record pointing to the Frond End Ip for the website DNS through your network provider – my case cloudflare

e.g test.albinsblog.com A-Record 34.120.141.59

Now test.albinsblog.com should be accessible through Google CDN, you can monitor the traffics through console and also the cache path can be invalidated whenever required.

google-cdn-motitoring


My backend server is running on Apache, I have enabled the Virtual host configuration to support test.albinsblog.com - Google CDN sends the DNS values as part of Host header, enable different virtual host to support multiple websites.

<VirtualHost *:80>
    ServerAdmin [email protected]
    DocumentRoot "C:\opt\communique\dispatcher\cache"
    ServerName test.albinsblog.com
    ServerAlias localhost

    RewriteEngine On

   RewriteRule ^/test.html$ http://test.albinsblog.com/content/wknd/fr/fr.html [L]

   <Directory />

Options Indexes FollowSymLinks Includes   
# Set includes to process .html files
AddOutputFilter INCLUDES .html
AddOutputFilterByType INCLUDES text/html
AllowOverride None
    </Directory>
</VirtualHost>



Conclusion


This concludes CDN is enabled on the custom origin website. The same setup can be used to support multiple websites, the backend(origin) should support the required DNS – Google CDN sends the Host header with the DNS value the user is accessing from the browser. The host header in the origin server can be used to enable the DNS specific functionalities. The Google CDN improves the website performance by caching and serving the website content from network of servers across the world, the server will be contacted in case the requested content is not available in the CDN cache.


Sunday, May 31, 2020

Social Login with LinkedIn - Adobe Experience Manager (AEM)

Social Login with LinkedIn - 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 LinkedIn 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. The scribe version shipped with AEM won’t support the LinkedIn OAuth 2.0 authentication flow but OAuth 1.0 is supported.
This tutorial explains the steps and the customization required to support the LinkedIn social login in AEM as Cloud version, the same should work with minimal change for other AEM versions.

Prerequisites

  • LinkedIn Developer Account
  • AEM as Cloud Publisher
  • WKND Sample Website
  • Git Terminal
  • Maven


LinkedIn Login Flow


Linkedin-aem-signin-flow

AEM Login URL 

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

LinkedIn Authorization Page URL

https://www.linkedin.com/oauth/v2/authorization?client_id=&redirect_uri=&scope=&response_type=code 
Access Token URL(GET) - https://www.linkedin.com/oauth/v2/accessToken? grant_type=authorization_code&client_id=&client_secret&code=<authorization code received>&redirect_uri=

Access Token URL(GET)

https://www.linkedin.com/oauth/v2/accessToken? grant_type=authorization_code&client_id=&client_secret&code=<authorization code received>&redirect_uri=

Retrieve Profile Data

https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName)? oauth2_access_token=<Access Token>


Steps

  • LinkedIn App Setup
  • Setup Custom LinkedIn 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


LinkedIn App Setup

As a first step, we should setup the OAuth app from LinkedIn. Login to https://www.linkedin.com/developers/ and click on Create App

create-oauth-app-linkedin


Enter your app name, link to an existing LinkedIn Company or create new one. Upload a logo for your App, accept the Legal agreement and click on Create App. You should verify the company page(Generate a Verification URL and click on the URL to approve the app association with the Company)

oauth-app-linkedin-conf

Add the redirect URL in Auth tab - http://localhost:4503/callback/j_security_check

oauth-app-linkedin-conf

On product tabs select “Sign in with LinkedIn”

oauth-app-linkedin-conf

This will take some time for approval, once approved you can see the required permissions under 
Auth

oauth-app-linkedin-conf

LinkedIn App is ready now, copy the Client ID and Client Secret(reveal the secret before copying) - these values required to enable the OAuth Authentication handler in AEM.

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-linkedin-service – name referred in LinkedinOAuth2ProviderImpl.java, change the name if required) 
Create a system user with name oauth-linkedin-service, navigate to http://localhost:4503/crx/explorer/index.jsp and login as an admin user and click on user administration

aem-service-user-configuration

aem-service-user-configuration


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-service-user-configuration

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

aem-service-user-mapping


Setup Custom LinkedIn OAuth Provider

As mentioned earlier AEM won’t support LinkedIn authentication OOTB, define a new provider to support the authentication with LinkedIn. Refer https://github.com/Adobe-Marketing-Cloud/aem-communities-oauth-sample/blob/87539ad4453f6589202c414ee14630382b146030/bundles/aem-communities-oauth-linkedin-provider/src/main/java/com/adobe/social/sample/oauth/impl/LinkedinProviderImpl.java#L350 for sample LinkedIn provider. 

The reference provider supports only OAuth 1.0, to support the OAuth 2.0 the provider should be modified and some extra overridden classes required as the AEM shipped scribe package wont support OAuth 2.0 for LinkedIn Provider. 

The LinkedIn Provider to support the OAuth 2.0 can be downloaded from - https://github.com/techforum-repo/bundles/tree/master/linkedin-oauth-provider

LinkedinOAuth2ProviderImpl.java – Provider class to support the LinkedIn authentication
LinkedinOAuth2Api.java – API class extended from default scribe DefaultApi20 to support LinkedIn OAuth 2.0 API integration
LinkedinOauth2ServiceImpl.java – Service class to get the Access Token from LinkedIn service response
LinkedinOauth2TokenExtracter.java – Extract the access token from LinkedIn authorization 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 linkedin-oauth-provider bundle – change the directory to bundles\linkedin-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 LinkedIn provider in config manager.

linkedin-oauth-provider-for-aem

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 LinkedIn

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 LinkedIn App
Client Secret - Copy the Client Secret value from LinkedIn App(Copy the secret by reveling the value)
Scope - r_liteprofile r_emailaddress
Provider ID – linkedin
Create users – Select the check box to create AEM users for LinkedIn profiles
Callback URL – the same value configured in LinkedIn App(http://localhost:4503/callback/j_security_check)

linkedin-oauth-provider-for-aem

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.
linkedin-oauth-provider-for-aem


Test the Login Flow

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

linkedin-oauth-provider-for-aem

The user should allow the access for first time

linkedin-oauth-provider-for-aem

Now you should be able to login to the WKND website

linkedin-oauth-provider-for-aem

The user profile is created in AEM

aem-oauth-user-details

aem-oauth-user-details


Whenever the profile data is changed (e.g firstName and LastName) in LinkedIn 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.

apache-jackrabit-oak-default-sync-handler


linkedin-oauth-provider-for-aem

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-encapsulated-token-support

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 LinkedIn profile but the same solution can be used with small changes to support different providers. The user authentication is enabled through OAuth 2.0 protocol as LinkedIn was already deprecated the OAuth 1.0. Custom providers and helper classes are required to support the authentication flow as AEM OOTB don’t support the authentication with LinkedIn but supports twitter and Facebook. Feel free to give your feed back and changes on the provider bundle.