Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuthException: Response body is incorrect. #327

Closed
angelique360 opened this issue Mar 27, 2017 · 9 comments
Closed

OAuthException: Response body is incorrect. #327

angelique360 opened this issue Mar 27, 2017 · 9 comments
Labels
Milestone

Comments

@angelique360
Copy link

Hi, The plugin throws the following error when trying to sign in with facebook.

error:500,
message:org.scribe.exceptions.OAuthException: Response body is incorrect. Can't extract a token from this: '{"access_token":"EAAOWKGC6MDcBAB9ZAka1zEc1","token_type":"bearer"}', error_description:org.scribe.exceptions.OAuthException: Response body is incorrect. Can't extract a token from this: '{"access_token":"EAAOWKGC6M","token_type":"bearer"}', error_code:OAuthException

@atifsaddique211f
Copy link

I am also getting the same issue for facebook login

@angelique360
Copy link
Author

Hello friend, I solve my problem by doing facebook login by hand, example:

   def code = params.code
   def query = "https://graph.facebook.com/v2.8/oauth/access_token?client_id=********&redirect_uri=*****&client_secret=****&code=${params.code}

 //call http
    def response = ApiClient.get(query)
   
    if(response?.code == HttpStatus.SC_OK){
        def access_token = JSON.parse(response.message)?.access_token

        def response2 = ApiClient.get("https://graph.facebook.com/v2.8/me?fields=id,name,first_name,middle_name,last_name,email,picture.height(200).width(200)&access_token="+access_token)
        

        if(response2?.code == HttpStatus.SC_OK){
            def profile =  JSON.parse(response2.message)

            def uid = profile?.id ?: null
            def first_name = profile?.first_name  ?: null
            def last_name = profile?.last_name  ?: null
            def email = profile?.email  ?: null
            def pictureUrl = profile?.picture  ? profile?.picture?.data?.url : null
  
            def userInstance = User.findByUsername(uid)
            def role = null

            if (!userInstance) {
               //Insert user

            } 

            springSecurityService.reauthenticate(userInstance.username)
           //use and import service userDetailsService
            def userDetail =  userDetailsService.loadUserByUsername(userInstance.username)
           //use and import service tokenGenerator
            def tokenValue = tokenGenerator.generateAccessToken(userDetail)
         
    //return or redirec
          return tokenValue

@atifsaddique211f
Copy link

@angelique360 Thanks a lot for sharing your solution. I am going to try it.

@sergioricardoptech
Copy link

sergioricardoptech commented Apr 17, 2017

I have the same problem...

package org.scribe.extractors;

import java.util.regex.*;

import org.scribe.exceptions.*;
import org.scribe.model.*;
import org.scribe.utils.*;

public class TokenExtractor20Impl implements AccessTokenExtractor
{
  private static final String TOKEN_REGEX = "access_token=([^&]+)";
  private static final String EMPTY_SECRET = "";
  /**
   * {@inheritDoc} 
   */
  public Token extract(String response)
  {
    Preconditions.checkEmptyString(response, "Response body is incorrect. Can't extract a token from an empty string");

    Matcher matcher = Pattern.compile(TOKEN_REGEX).matcher(response);
    if (matcher.find())
    {
      String token = OAuthEncoder.decode(matcher.group(1));
      return new Token(token, EMPTY_SECRET, response);
    } 
    else
    {
      throw new OAuthException("Response body is incorrect. Can't extract a token from this: '" + response + "'", null);
    }
  }
}

This class use TOKEN_REGEX for extract the token but now the respose is a Json.

https://developers.facebook.com/docs/apps/changelog

[Oauth Access Token] Format - The response format of https://www.facebook.com/v2.3/oauth/access_token returned when you exchange a code for an access_token now return valid JSON instead of being URL encoded. The new format of this response is {"access_token": {TOKEN}, "token_type":{TYPE}, "expires_in":{TIME}}. We made this update to be compliant with section 5.1 of RFC 6749.

@Prakash-Thete
Copy link

Prakash-Thete commented Apr 24, 2017

I had the same problem. So basically response from facebook is now coming as JSON.

The solution I have derived is to write our own custom facebook Api like below

Create the class FacebookCustomApi under /src/java/

public class FacebookCustomApi extends DefaultApi20{

    private static final String AUTHORIZE_URL           = "https://www.facebook.com/dialog/oauth?response_type=code&client_id=%s&redirect_uri=%s";
    private static final String SCOPED_AUTHORIZE_URL    = AUTHORIZE_URL + "&scope=%s";

    @Override
    public String getAccessTokenEndpoint() {
        return "https://graph.facebook.com/oauth/access_token";
    }

    @Override
    public AccessTokenExtractor getAccessTokenExtractor() {
        return new AccessTokenExtractor() {

            @Override
            public Token extract(String response) {

                Preconditions.checkEmptyString(response, "Response body is incorrect. Can't extract a token from an empty string");
                try {
                    //Here is the real deal, Just create the JSON from response and get the access token from it
                    JSONObject json = new JSONObject(response);
                    String token = json.getString("access_token");

                    return new Token(token, "", response);
                } catch (Exception e){
                    throw new OAuthException("Response body is incorrect. Can't extract a token from this: '" + response + "'", null);
                }
            }
        };
    }

    @Override
    public String getAuthorizationUrl(OAuthConfig config) {
        // Append scope if present
        if (config.hasScope()) {
            return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()),
                    OAuthEncoder.encode(config.getScope()));
        } else {
            return String.format(AUTHORIZE_URL, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()));
        }
    }

    @Override
    public Verb getAccessTokenVerb() {
        return Verb.POST;
    }

    @Override
    public OAuthService createService(OAuthConfig config) {
        return new FacebookOAuth2Service(this, config);
    }

    private class FacebookOAuth2Service extends OAuth20ServiceImpl {

        private static final String GRANT_TYPE_AUTHORIZATION_CODE   = "authorization_code";
        private static final String GRANT_TYPE                      = "grant_type";
        private DefaultApi20 api;
        private OAuthConfig config;

        public FacebookOAuth2Service(DefaultApi20 api, OAuthConfig config) {
            super(api, config);
            this.api    = api;
            this.config = config;
        }

        @Override
        public Token getAccessToken(Token requestToken, Verifier verifier) {
            OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
            switch (api.getAccessTokenVerb()) {
                case POST:
                    request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
                    request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
                    request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
                    request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
                    request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE);
                    break;
                case GET:
                default:
                    request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
                    request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
                    request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue());
                    request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
                    if (config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope());
            }
            Response response = request.send();
            return api.getAccessTokenExtractor().extract(response.getBody());
        }
    }
}

And use it in our oauth config as below

//for facebook authentication

oauth {
    providers {
        facebook {
            api = FacebookCustomApi

@alvarosanchez alvarosanchez added this to the 2.0.0.M3 milestone Apr 24, 2017
@hamza3202
Copy link

@Prakash-Thete Your solution doesn't seem to be working for me, i am unable to over-ride the API? any guesses why?

@ivarprudnikov
Copy link

ivarprudnikov commented May 8, 2017

Instead of overriding whole of FacebookApi class I searched for the way to replace AccessTokenExtractor getAccessTokenExtractor() method which by default uses org.scribe.extractors.TokenExtractor20Impl, I wanted to replace it with org.scribe.extractors.JsonTokenExtractor

It was a bit odd as class that needs to have method overridden ExtendedFacebookApi is declared final so had to rewrite it:

OverridenFacebookApi.groovy

package foobar

import org.scribe.builder.api.StateApi20
import org.scribe.extractors.AccessTokenExtractor
import org.scribe.extractors.JsonTokenExtractor
import org.scribe.model.OAuthConfig
import org.scribe.utils.OAuthEncoder
import org.scribe.utils.Preconditions

class OverridenFacebookApi extends StateApi20 {

    private static final String AUTHORIZE_URL_WITH_STATE = "https://www.facebook.com/dialog/oauth?display=popup&client_id=%s&redirect_uri=%s&state=%s";
    private static final String SCOPED_AUTHORIZE_URL_WITH_STATE = AUTHORIZE_URL_WITH_STATE + "&scope=%s";

    @Override
    AccessTokenExtractor getAccessTokenExtractor() {
        return new JsonTokenExtractor()
    }

    @Override
    String getAccessTokenEndpoint() {
        return "https://graph.facebook.com/oauth/access_token";
    }

    @Override
    String getAuthorizationUrl(final OAuthConfig config,
                                      final String state) {
        Preconditions
                .checkEmptyString(config.getCallback(),
                "Must provide a valid url as callback. Facebook does not support OOB");

        // Append scope if present
        if (config.hasScope()) {
            return String.format(SCOPED_AUTHORIZE_URL_WITH_STATE,
                    config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()),
                    OAuthEncoder.encode(state),
                    OAuthEncoder.encode(config.getScope()));
        } else {
            return String.format(AUTHORIZE_URL_WITH_STATE, config.getApiKey(),
                    OAuthEncoder.encode(config.getCallback()),
                    OAuthEncoder.encode(state));
        }
    }

}

Then had to make sure FacebookClient is using it, so I've extended it and changed initialisation to use the class above.

OverridenFacebookClient.groovy

package foobar

import groovy.transform.InheritConstructors
import org.apache.commons.lang3.StringUtils
import org.pac4j.core.util.CommonHelper
import org.pac4j.oauth.client.FacebookClient
import org.scribe.model.OAuthConfig
import org.scribe.model.SignatureType
import org.scribe.oauth.StateOAuth20ServiceImpl

@InheritConstructors
class OverridenFacebookClient extends FacebookClient {

    @Override
    protected void internalInit() {
        super.internalInit();
        CommonHelper.assertNotBlank("fields", this.fields);
        this.api20 = new OverridenFacebookApi();
        if(StringUtils.isNotBlank(this.scope)) {
            this.service = new StateOAuth20ServiceImpl(this.api20, new OAuthConfig(this.key, this.secret, this.callbackUrl, SignatureType.Header, this.scope, (OutputStream)null), this.connectTimeout, this.readTimeout, this.proxyHost, this.proxyPort);
        } else {
            this.service = new StateOAuth20ServiceImpl(this.api20, new OAuthConfig(this.key, this.secret, this.callbackUrl, SignatureType.Header, (String)null, (OutputStream)null), this.connectTimeout, this.readTimeout, this.proxyHost, this.proxyPort);
        }

    }
}

Finally had to reference it in Config.groovy

import foobar.OverridenFacebookClient

grails.plugin.springsecurity.rest.oauth.facebook.client = OverridenFacebookClient

@pabloalba
Copy link

Awesome workaround @ivarprudnikov !

Just a fix. On OverridenFacebookClient.internalInit you miss the context param. It should be:

    @Override
    protected void internalInit(final WebContext context) {
        ...
    }

@alvarosanchez
Copy link
Member

After upgrading pac4j (#416), the Facebook API supported now is 2.11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants