403 Response From Adobe Experience Manager OAuth 2 Token Endpoint

1.8k views Asked by At

I am using Postman to test OAuth 2 from a vanilla AEM install.

enter image description here

Postman can successfully obtain the authorization code from /oauth/authorize after I grant access:

enter image description here

But when it tries to use the code to obtain a token from /oauth/token it receives the following response:

HTTP ERROR: 403 Problem accessing /oauth/token. Reason: Forbidden Powered by Jetty://

Looking in Fiddler it is doing a POST to /oauth/token with the following Name/Values in the body:

client_id: Client ID from /libs/granite/oauth/content/client.html

client_secret: Client Secret from /libs/granite/oauth/content/client.html

redirect_uri: https://www.getpostman.com/oauth2/callback

grant_type: authorization_code

code: Code returned from previous request to oauth/authorize

Am I missing something?

3

There are 3 answers

0
GerardBeckerleg On BEST ANSWER

I found the answer myself and thought I'd share the process I went through as well as the answer because it might help other people new to AEM.

How to find the cause of the error:

  1. Go to CRXDE Lite.
  2. Select console.
  3. Then deselect the stop button to allow new console logs to appear (this is very counter-intuitive to me).

CRXDE Lite Console

From here I was able to see the cause of the issue:

org.apache.sling.security.impl.ReferrerFilter Rejected empty referrer header for POST request to /oauth/token

Because postman does not place a referrer in the request header I had to tell Apache Sling to allow empty request headers.

To do this:

  1. Go to /system/console/configMgr
  2. Open the Apache Sling Referrer Filter Config
  3. Select the Allow Empty check box

Apache Sling Referrer Filter Config

3
Suren Konathala On

Would help if you can list some code snippets on how you are building the url and fetching the token.

Here's an example of how we've implemented very similar to what you are trying to do, maybe it'll help.

Define a service like below (snippet) and define the values (host, url, etc) in OSGI (or you can also hard code them for testing purposes)

     @Service(value = OauthAuthentication.class)
     @Component(immediate = true, label = "My Oauth Authentication", description = "My Oauth Authentication", policy = ConfigurationPolicy.REQUIRE, metatype = true)
     @Properties({
       @Property(name = Constants.SERVICE_VENDOR, value = "ABC"),
       @Property(name = "service.oauth.host", value = "", label = "Oauth Host", description = "Oauth Athentication Server"),
       @Property(name = "service.oauth.url", value = "/service/oauth/token", label = "Oauth URL", description = "Oauth Authentication URL relative to the host"),
       @Property(name = "service.oauth.clientid", value = "", label = "Oauth Client ID", description = "Oauth client ID to use in the authentication procedure"),
       @Property(name = "service.oauth.clientsecret", value = "", label = "Oauth Client Secret", description = "Oauth client secret to use in the authentication procedure"),
       @Property(name = "service.oauth.granttype", value = "", label = "Oauth Grant Type", description = "Oauth grant type") })
      public class OauthAuthentication {   
      ...
      @Activate
      private void activate(ComponentContext context) {
         Dictionary<String, Object> properties = context.getProperties();
         host = OsgiUtil.toString(properties, PROPERTY_SERVICE_OAUTH_HOST,new String());

         // Similarly get all values
         url = 
         clientID = 
         clientSecret = 
         grantType = 
         authType = "Basic" + " "+ Base64.encode(new String(clientID + ":" + clientSecret));
      }

      public static void getAuthorizationToken(
         try {
            UserManager userManager = resourceResolver.adaptTo(UserManager.class);
            Session session = resourceResolver.adaptTo(Session.class);

            // Getting the current user                        
            Authorizable auth = userManager.getAuthorizable(session.getUserID());

         user = auth.getID();
         password = ...
         ... 
         ...
         String serviceURL = (host.startsWith("http") ? "": protocol + "://") + host + url;
         httpclient = HttpClients.custom().build();
         HttpPost httppost = new HttpPost(serviceURL);

         // set params
         ArrayList<BasicNameValuePair> formparams = new ArrayList<BasicNameValuePair>();
         formparams.add(new BasicNameValuePair("username", user));
         formparams.add(new BasicNameValuePair("password", password));
         formparams.add(new BasicNameValuePair("client_id", clientID));
         formparams.add(new BasicNameValuePair("client_secret",clientSecret));
         formparams.add(new BasicNameValuePair("grant_type",grantType));

          UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(formparams, "UTF-8");
          httppost.setEntity(postEntity);

          // set header
          httppost.addHeader("Authorization", authType);
          response = httpclient.execute(httppost);
          HttpEntity entity = response.getEntity();

          if (response.getStatusLine().getStatusCode() == 200) {
            if (entity != null) {
               object = new JSONObject(EntityUtils.toString(entity));
            }
            if (object != null) {
              accessToken = object.getString("access_token");
              ////
            }
          }
      }
0
SA007 On

Good way to allow this to list the allowed hosts, otherwise this is against best practices for AEM security checklist.

Its fine for development environment not for production.