March 22, 2020
Estimated Post Reading Time ~

AEM as OAuth Server – Part 1 – Setting Up Scopes

Usually, when we want to implement OAuth in AEM, our requirements are that a user should be able to use their Facebook or Google account to show certain details- like profile pic from their social account in the AEM webpage. In these cases, AEM is an OAuth Client and Facebook or Google is the OAuth server. Here the user in AEM is prompted to log in to Facebook or Google and authorize AEM to access parts of their Facebook profile or Google account (known as scope of access).

This requirement that is described here is the opposite of that – We have a client who needs the authorization to access part of the AEM content. In this case, AEM is the OAuth server.

OAuth 2.0 Authorization Framework
This is the OAuth 2.0 Authorization Framework: https://tools.ietf.org/html/rfc6749
Roles in OAuth Authentication is described here: https://tools.ietf.org/html/rfc6749#section-1.1
Protocol Flow – Authorization Grant method is described here: https://tools.ietf.org/html/rfc6749#section-1.3.1

Adobe’s Granite OAuth2 Server Implementation
Details about Adobe’s Granite OAuth2 Server implementation that we should know: This is as told by Adobe Support –
This is a partial implementation of The OAuth 2.0 Authorization Framework (RFC 6749) (http://tools.ietf.org/html/rfc6749)
In particular, it (currently) implements only the Authorization Code Grant flow (http://tools.ietf.org/html/rfc6749#section-4.1). It also implements ONLY the “Authorization Request Header Field” (http://tools.ietf.org/html/rfc6750#section-2.1), while the “Form-Encoded Body Parameter” and “URI Query Parameter” are not implemented.
These are the existing documents available for AEM as OAuth Server which gives us insights on how we should go about implementing Scopes and our Rest APIs, however, these are for AEM 6.0 and cannot be applied completely for 6.2 since the Granite OAuth2 server implementation has changed.
· https://docs.adobe.com/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea.html
· https://docs.adobe.com/content/ddc/en/gems/oauth-server-functionality-in-aem—embrace-federation-and-unlea/_jcr_content/par/download/file.res/OAuth_Server_functionality_in_AEM%207%2023%2014.pdf

How to set up AEM as the OAuth Server
Setup OAuth Client in AEM

OAuth clients can be set up here : Tools -> Security -> OAuth clients.
Once an OAuth client is set up, the client id and client secret are auto-generated.
Please note that the redirect url needs to be set exactly as provided by the client. Apart from the client id and client secret, redirect url is one of the parameters that is used to validate an OAuth client and it must exactly match.
This is a sample client that was set up with the tool “Postman” as the client.

In order to get the callback url of postman for OAuth –
Create a new request in Postman and for Authorization Type use – OAuth 2.0. This shows an option – “Get New Access Token”
The following pop up appears when we click on it. The first entry is the callback url. We will look at all the entries in detail in later sections.


The Callback Url for Postman is: https://www.getpostman.com/oauth2/callback
Setup a client using this callback url.


After clicking on Create Client ID, the client id and client secret are auto-generated by AEM.


Now an Oauth Client for Postman is set up in the AEM server.
Enable Adobe Granite OAuth Server Authentication Handler
We have to make sure that the Adobe Granite OAuth Server Authentication Handler configuration is enabled (com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler)

http://localhost:4504/system/console/configMgr/com.adobe.granite.oauth.server.auth.impl.OAuth2ServerAuthenticationHandler



Set up Scopes that the client should be able to access
Implement Scope
Scopes have to be coded by implementing com.adobe.granite.oauth.server.Scope.
Scope implementation Example

package com.techaspect.oAuthExample.core.oauth.scope;

import javax.servlet.http.HttpServletRequest;
import org.apache.jackrabbit.api.security.user.User;
import com.adobe.granite.oauth.server.Scope;

public class SampleScopeOne implements Scope {

@Override
public String getDescription(HttpServletRequest httpServletRequest) {
return "This is a sample scope - One";
}

@Override
public String getEndpoint() {
return "/bin/oauth/sample/one";
}

@Override
public String getName() {
return "sampleScopeOne";
}

@Override
public String getResourcePath(User user) {
return "/content/dam/sample/one";
}

}

Implement OAuth2ResourceServer
To set up scopes, the com.adobe.granite.oauth.server.OAuth2ResourceServer has to be re-implemented. This interface has a method – public Map<String, Scope> getAllowedScopes() which should return the new scopes as needed.
Example of OAuth2ResourceServer implementation

package com.techaspect.oAuthExample.core.oauth.impl;

import java.util.HashMap;
import java.util.Map;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.oauth.server.OAuth2ResourceServer;
import com.adobe.granite.oauth.server.Scope;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeOne;
import com.techaspect.oAuthExample.core.oauth.scope.SampleScopeTwo;

@Component(label = "SampleOAuth2ResourceServerImpl - OAuth Resource Server", description = "OAuth Resource Server", metatype = true, immediate = true)
@Service
public class OAuth2ResourceServerSampleImpl implements OAuth2ResourceServer {

@Property(intValue = 20000)
private static final String RANKING = "service.ranking";

private static final Logger LOG = LoggerFactory.getLogger(OAuth2ResourceServerSampleImpl.class);
private static Map<String, Scope> allowedScopes = createMap();

private static Map<String, Scope> createMap() {
Map<String, Scope> myMap = new HashMap<String, Scope>();
myMap.put("sampleScopeOne", new SampleScopeOne());
myMap.put("sampleScopeTwo", new SampleScopeTwo());
return myMap;
}

@Override
public Map<String, Scope> getAllowedScopes() {

if(LOG.isDebugEnabled()) {
LOG.debug("List of allowed scopes from getAllowedScopes() : ");
for (Map.Entry<String, Scope> entry : allowedScopes.entrySet())
{
LOG.debug("Allowed Scope : " + entry.getKey() + " : " + entry.getValue().getEndpoint());
}
}

return allowedScopes;
}
}

Sample Endpoint
package com.techaspect.oAuthExample.core.servlets;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;

/**
* Servlet that writes some sample content into the response. It is mounted for
* all resources of a specific Sling resource type. The
* {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
* idempotent. For write operations use the {@link SlingAllMethodsServlet}.
*/
@Component(service = Servlet.class, property = { Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET, "sling.servlet.paths=" + "/bin/oauth/sample/one" })
public class SampleOAuthTestServlet extends SlingSafeMethodsServlet {

private static final long serialVersionUID = 4077423361846561555L;

@Override
protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
throws ServletException, IOException {

resp.setContentType("text/plain");
resp.getWriter().write("SampleOAuthTestServlet - 1 - /bin/oauth/sample/one!! : User ID : "
+ req.getResourceResolver().getUserID());
}
}
Authorization and Token URLs for OAuth in AEM
Authorization url : /oauth/authorize
Token Url : /oauth/token
Example :
Authorization url

http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback

Token Url
The token call is a post request and would be something like this. The code is the code Json Web Token – JWT returned by the authorization endpoint.
We will see this in more detail in the Testing Blog.


Disable default implementation of OAuth2ResourceServer
Ideally, after implementing the Scopes and implementing the OAuth2ResourceServer, we should be done and should be ready to test the setup, however, there is one more small step that needs to be done.
In the previous step, we have re-implemented/customized com.adobe.granite.oauth.server.OAuth2ResourceServer to return the new custom scopes. We have set the ranking of this new implementation of OAuth2ResourceServer to 20,000, hoping that this implementation will definitely override the default implementation of OAuth2ResourceServer (which is com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl).

However, this is not the case.
The default implementation of OAuth2ResourceServer has to be disabled before our implementation is picked up by the OAuth2AuthorizationEndpointServlet.
Here are more details :
Before doing this step (disabling of the default implementation of OAuth2ResourceServer ) try to access the Authorization url: It would return 400 Bad requests with the reason that the scope is invalid. However, the scope is correct as per our custom implementation.

http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2



To troubleshoot this a bit, check out the OAuth2AuthorizationEndpointServlet. Notice that the reference for oAuth2ResourceServer is still the default – com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl. This default implementation does not have the new scopes that we implemented. This is the reason the authorization url/endpoint returns the invalid scope message.

http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet


Check out the default implementation of OAuth2ResourceServer – OAuth2ResourceServerImpl. We have to disable this so that OAuth2AuthorizationEndpointServlet refers to our implementation of OAuth2ResourceServer

http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2ResourceServerImpl


Disable this default implementation.

After disabling the default implementation of OAuth2ResourceServer, again see the OAuth2AuthorizationEndpointServlet. Notice that, now the reference for oAuth2ResourceServer refers to our custom implementation.

http://localhost:4504/system/console/components/com.adobe.granite.oauth.server.impl.OAuth2AuthorizationEndpointServlet


Now try the Authorization url again: Now it shows the authorization screen asking for our approval. The description of the scopes we setup is shown here.

http://localhost:4504/oauth/authorize?client_id=p1huuknrm19olc2ppbh7lrrjg2-iqelpf81&scope=sampleScopeOne%20sampleScopeTwo&response_type=code&redirect_uri=https://www.getpostman.com/oauth2/callback


And now we are done with setting up an OAuth client and setting up scopes and are ready to test this out.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.