AEM Social Login (Google OAuth2)
Tags: java, webdev, aem
Published: 2020-05-31 14:26:14

***Tested on AEM 6.5.0***

As we all know that AEM provides multiple types of [Authentication](https://docs.adobe.com/content/help/en/experience-manager-learn/foundation/authentication/authentication-support-article-understand.html) out of the box using Sling's [AuthenticationHandler](https://sling.apache.org/documentation/the-sling-engine/authentication/authentication-authenticationhandler.html) API.

Today we will see how we can utilise OAuth Authentication Handler to integrate Google OAuth2. All the code required for this is available on [GitHub](https://github.com/thegreyteacher/aem-social-login).

##Prerequisites
1. Understanding of [OAuth 2 Authorizaion code grant type](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2)
2. Client Id and secret of an Application registered on [Google dev console](https://console.developers.google.com/).
   * In Allowed Redirect URIs add: <your-domain>/callback/j_security_check. Eg.- localhost:4503/callback/j_security_check
3. Basic understanding of [scribe-java](https://github.com/scribejava/scribejava) library. 
*Alert - AEM uses very old version of [scibe-java](https://mvnrepository.com/artifact/org.scribe/scribe/1.3.7) library which we can't upgrade if we want to utilise the OOTB OAuthAuthenticationHandler*


**OAuth Authentication Handler Background**
AEM developed its whole OAuth flow around the Scribe-Java library and provides OOTB working solution for [Facebook and Twitter](https://docs.adobe.com/content/help/en/experience-manager-65/communities/administer/social-login.html) which we can test on We.Retail sample site in publish mode. 
We can also refer to *com.adobe.granite.auth.oauth.impl.OAuthAuthenticationHandler* class available in *Adobe Granite OAuth Authentication Handler(com.adobe.granite.auth.oauth)* bundle.
![OAuth  Bundle on System console](https://dev-to-uploads.s3.amazonaws.com/i/wzk025lwy0p5lbkm8qm8.PNG).
The auth is based on 4 configurations primarily which we will discuss below.

###*Provider Implementation*
The first thing is, we have to implement *[com.adobe.granite.auth.oauth.Provider](https://helpx.adobe.com/experience-manager/6-5/sites/developing/using/reference-materials/javadoc/com/adobe/granite/auth/oauth/Provider.html)* interface which will provide all the details of the Identity provider, in our case, it is Google. This implementation must be registered as an OSGi service which must have a property with name *oauth.provider.id* with a unique value which will be used to map the URL/ProviderConfig to this Provider. Please refer to our [github repo](https://github.com/thegreyteacher/aem-social-login/blob/master/core/src/main/java/com/tgt/core/aem/social/login/providers/impl/GoogleProvider.java) for the implementation. AEM provides the same Provider implementation for Facebook and Twitter.

###*OAuth Authentication Handler Configuration*
1. Enable OAuth Authentication Handler by saving configuration for *Adobe Granite OAuth Authentication Handler* on system console. We can define the repository path on which we want to activate this auth handler. In our case we will use the default one i.e. "/" to enable it on the complete repository.
    ![Adobe Granite OAuth Handler Config](https://dev-to-uploads.s3.amazonaws.com/i/rij3nrhz5hqt3h2p4zwg.PNG)
2. Configure IDP (Identity/social Provider):
   1. Configure the provider details in *Adobe Granite OAuth Application and Provider* factory config.
        *    ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/r9k6cag5oy9j1ska7vsv.PNG)
        * Config ID -> unique ID for the provider configuration. This will be used in the URL to trigger the OAuth flow for this provider
        * Client ID -> Registered App client ID
        * Client Secret -> Registered App client secret
        * Scope -> Scopes to be used in the OAuth flow
        * Provider ID -> oauth.provider.id property configured for our Provider. This will map our Provider implementation to this Provider config.
        * Create users -> Whether the authenticated users will be created in AEM or not. In our case we will enable this.
        * User ID property -> Social provider property which will be used to map the IDP (Identity Provider) user to AEM user. This is an additional feature which can be used to override the Provider.getUserIdProperty value. In our case we will use our Provider's getUserIdProperty method value to map the user. So, we will keep this empty.
        * Force Strict Username Mapping -> Enable this so that the Auth handler uses the authorizableId of the AEM user to map the user.
        * Encode UserIds -> Uncheck this because we want to keep the user email as it is.
        * Hash UserIds -> Uncheck this because we want to keep the user email as it is.
        * Callback URL -> AEM has hardcoded this to "/callback/j_security_check". So keep this empty and make sure to add "/callback/j_security_check" in your app's allowed Redirect URIs as well.
        * Save access token -> Save access token in encrypted form on user's profile. In our case we will save it.
        * Save access token in the Cookies -> Save access token in encrypted form in the cookies. In our case we will disable it.
        * Enable Cross-Site Request Forgery state protection -> This will enable the state parameter during OAuth flow. In our case we will enable it for improved security.
        * Enable Persisting Request Params in Callback URL -> Add the request params in Callback URL. In our case we will disable this. Please keep in mind that if you enable this then you have to update your callback URL in the registered app configuration accordingly on the Google dev console or any other social provider which you are using.
        * Enable siblings configurations support -> In case you have multiple configs for same social provider and you want to reuse them during authentication. In our case we will disable this.
   **Note:** As soon as you save this configuration AEM will create the below two configuration automatically. In case you are making the below two configuration as part of your code or updating the existing one on configMgr then please make sure you use the same value for oauth.config.id of this configuration and idp.name, sync.handlerName in ExternalLoginModuleFactory and handler.name in DefaultSyncHandler.
   2. Configure DefautlSyncHandler in *Apache Jackrabbit Oak Default Sync Handler* factory config.
      * ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/352qoi5cyxzwbyzemz84.PNG)
      * Sync Handler Name -> As mentioned in the above note, this must be same oauth.config.id. In our case it will be tgt-google.
      * User auto membership -> The user will be added to these groups automatically. In case we want to add the users in the group on the basis of some conditions then we can utilise the 'onUserCreate' method of the Provider implementation.
      * User property mapping -> The properties map returned from the Provider's mapProperties method will be used here to save the properties on the user profile. The left part (before '=') will be the relative path of the property from the user's node under /home/users and the right part (after '=') is the key of the map.
      * User Path Prefix -> The relative path from /home/users where the user will be created. Please note that AEM internally creates a folder with first 4 chars of the user ID and add the user under that folder. So, for example, in our case we have added *thegreyteacher* here and let's say a user with ID *thegreyteacher@gmail.com * is logged in then it will be created in the folder */home/users/thegreyteacher/theg*.
      * Disable missing users -> If a user logged in again and it is not found on the IDP then it will be removed from AEM as well if this is unchecked. We don't want to remove the user and its data from AEM so we will check this to only disable the user instead of removing it.
   3. Configure External Login Module in *Apache Jackrabbit Oak External Login Module* factory config.
      * ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rhn9r3bkv5c048p09hwv.PNG)
      * JAAS Ranking -> 150 (default)
      * JASS Control Flag -> SUFFICIENT (default). This must be SUFFICIENT.
      * JAAS Realm -> empty (default)
      * Identity Provider Name -> As mentioned in the note above, this must be same as oauth.config.id. In our case it will be tgt-google.
      * Sync Handler Name -> As mentioned in the note above, this must be same as oauth.config.id. In our case it will be tgt-google.

###**Trigger OAuth flow**
After all the above configuration, OAuth flow will be triggered for all the URLs having pattern:
*{your-domain}/any_path/j_security_check?configid={oauth.config.id property value}*. Example: *localhost:4503/j_security_check?configid=tgt-google*.


##Some chaos
1. In case you want to use email Id as the authorizableId of the AEM user and planning to integrate multiple social/identity providers like Google, Twitter, Facebook, GitHub etc. then it will not work because **a user can only be authenticated using a single identity provider**. AEM out of the box integration for Facebook and Twitter also adds a provider specific prefix to the user ID to create a different authorizable ID and hence the different user for all IDPs even though the email ID of the user is same on different IDPs.
There is no way to override this behaviour because AEM *com.adobe.granite.auth.oauth.impl.OAuthIdentityProvider* validator validates the user on the basis of the Provider which is too much coupled with OAuth Authentication handler. So, to implement this behaviour we have to write our own [Custom Authentication Handler](https://helpx.adobe.com/experience-manager/using/twofactor64.html) from scratch. Please mention your implementation in comments.
2. Make sure to use the same property for oauth.config.id in provider config and handler.name in DefaultSyncHandler and idp.name, sync.handlerName in ExternalLoginModuleFactory otherwise as soon as you save this configuration AEM will create the DefualtSyncHandler and ExternalLoginModule configurations automatically.