Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring : Google authentication redirect_uri_mismatch and URL wont open on browser

I am working on a Spring-MVC application running on tomcat in which I would like to use Google drive functionality. I tried with a service account on my local machine and I had no problems. But when I uploaded the code on server, the browser URL wont be opened. Then I thought, I should not use a service account, I should use a normal web-application account. Now when I do that, I get a redirect_uri_mismatch.

I don't understand one thing, I am setting the redirect URL in flow, in the JSON, why on earth is it getting the redirect_url with random port numbers. If I change the port number in the browser URL, it works fine. But still on server it wont open the browser url, I can see it in tomcat logs, but the damn thing does not open the URL.

Here are my redirect URL from Google app :

http://localhost/authorizeuser
http://localhost:8080/
http://localhost:8080
http://localhost
http://localhost:8080/Callback
https://testserver.net/Callback
http://testserver.net/Callback
http://127.0.0.1

Here is my client_secret.json :

{"web": {
    "client_id": "clientid",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_email": "clientemailstuff",
    "client_x509_cert_url": "certurlstuff",
    "client_secret": "itsasecret",
    "redirect_uris": ["http://localhost:8080/","http://localhost:8080"],
    "javascript_origins": ["https://testserver.net", "http://testserver.net","http://localhost:8080"]
}}

And here is the code where I am trying to authenticate :

 @Override
    public Credential authorize() throws IOException {
        InputStream in =
                DriveQuickstartImpl.class.getResourceAsStream("/client_secret.json");
        GoogleClientSecrets clientSecrets =
                GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        GoogleAuthorizationCodeFlow flow =
                new GoogleAuthorizationCodeFlow.Builder(
                        HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                        .setDataStoreFactory(DATA_STORE_FACTORY)
                        .setAccessType("offline")
                        .build();
        flow.newAuthorizationUrl().setState("xyz").setRedirectUri("http://localhost:8080/Callback");
        Credential credential = new AuthorizationCodeInstalledApp(
                flow, new LocalServerReceiver()).authorize("user");

        if(credential!=null && credential.getRefreshToken() != null){
            storeCredentials(credential);
        }
        return credential;
    }

This is majorly pissing me off as I am setting the redirect url, and it is just being ignored and why on earth a browser tab wont be opened when application is deployed on server.

Update Spring problem also fixed, the below code can be used for GoogleDrive authorization on a server with tomcat or others.

@Service
@Transactional
public class GoogleAuthorization{


    @Autowired
    private DriveQuickstart driveQuickstart;

    private static final String APPLICATION_NAME ="APPNAME";

    private static final java.io.File DATA_STORE_DIR = new java.io.File(
            "/home/deploy/store");

    private static FileDataStoreFactory DATA_STORE_FACTORY;

    private static final JsonFactory JSON_FACTORY =
            JacksonFactory.getDefaultInstance();

    private static HttpTransport HTTP_TRANSPORT;

    private static final List<String> SCOPES =
            Arrays.asList(DriveScopes.DRIVE);

    private static final String clientid = "clientid";
    private static final String clientsecret = "clientsecret";

    private static final String CALLBACK_URI = "http://localhost:8080/getgooglelogin";

    private String stateToken;

    private final GoogleAuthorizationCodeFlow flow;

    public GoogleAuthorization(){
        try {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
            DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);

        } catch (GeneralSecurityException | IOException e) {
            e.printStackTrace();
        }

        flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT,
                JSON_FACTORY, clientid, clientsecret, SCOPES).setAccessType("offline").setApprovalPrompt("force").build();
        generateStateToken();

    }



    /**
     * Builds a login URL based on client ID, secret, callback URI, and scope
     */
    public String buildLoginUrl() {

        final GoogleAuthorizationCodeRequestUrl url = flow.newAuthorizationUrl();

        return url.setRedirectUri(CALLBACK_URI).setState(stateToken).build();
    }

    /**
     * Generates a secure state token
     */
    private void generateStateToken(){
        SecureRandom sr1 = new SecureRandom();
        stateToken = "google;"+sr1.nextInt();
    }

    /**s
     * Accessor for state token
     */
    public String getStateToken(){
        return stateToken;
    }

    /**
     * Expects an Authentication Code, and makes an authenticated request for the user's profile information
     * * @param authCode authentication code provided by google
     */
    public void saveCredentials(final String authCode) throws IOException {

        GoogleTokenResponse response = flow.newTokenRequest(authCode).setRedirectUri(CALLBACK_URI).execute();
        Credential credential = flow.createAndStoreCredential(response, null);
        System.out.println(" Credential access token is "+credential.getAccessToken());
        System.out.println("Credential refresh token is "+credential.getRefreshToken());
// The line below gives me a NPE.
        this.driveQuickstart.storeCredentials(credential);
    }
}

Controller method :

  @RequestMapping(value = "/getgooglelogin")
    public String getGoogleLogin(HttpServletRequest request, HttpServletResponse response, HttpSession session,Model model) {
// Below guy should be autowired if you want to use Spring. 
        GoogleAuthorization helper = new GoogleAuthorization();

        if (request.getParameter("code") == null
                || request.getParameter("state") == null) {

            model.addAttribute("URL", helper.buildLoginUrl());
            session.setAttribute("state", helper.getStateToken());

        } else if (request.getParameter("code") != null && request.getParameter("state") != null && request.getParameter("state").equals(session.getAttribute("state"))) {
            session.removeAttribute("state");

            try {
                helper.saveCredentials(request.getParameter("code"));
                return "redirect:/dashboard";
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "newjsp";
    }

newjsp just has a button to click on the URL.

like image 562
We are Borg Avatar asked Jun 30 '15 08:06

We are Borg


People also ask

What does Error 400 Redirect_uri_mismatch mean?

This error typically means the Client Redirect URL was not properly added to the OAuth Web Application in the Google Cloud Console. To resolve this, the user will need to copy the Client Redirect URL from the Single Sign-On Settings page from ThinkCentral, my.hrw.com, or HMH Ed.

Does google support pkce?

Google's documentation for "Mobile and Desktop apps" does direct developers to use a PKCE Authorization Code flow. Clients using Google Android, iOS or windows store credential types with PKCE may omit the client_secret (see the note on the refresh token parameter table - and confirmed by Cristiano).

How does redirect URI work?

A redirect URI, or reply URL, is the location where the authorization server sends the user once the app has been successfully authorized and granted an authorization code or access token.


2 Answers

Specifically, you're getting random ports because you are using LocalServerReceiver, which starts up a jetty instance on a free port in order to receive an auth code.

At a higher level, it looks like you are developing a web server application, but you are trying to use Google OAuth as if it were an installed application. If you are indeed making a web server application, you should be using your server's host name instead of localhost in your callback URL, providing a link for the end user to authenticate using flow.newAuthorizationUrl(), and have your callback fetch the token using flow.newTokenRequest(String). Also make sure that the Client ID you created in your console is of type Web application, or you'll get redirect_uri_mismatch errors. A full working example of how to do this can be found here.

like image 135
heenenee Avatar answered Sep 29 '22 20:09

heenenee


Instead of using:

Credential credential = new AuthorizationCodeInstalledApp( flow, 
                     new LocalServerReceiver).authorize("user");

Use

LocalServerReceiver localReceiver = new LocalServerReceiver.
                                        Builder().setPort(XXXX).build();

for setting a static port number

Credential credential = new AuthorizationCodeInstalledApp( flow,
                                      localReceiver).authorize("user");

although you wont be able to change redirect url, however you can set the host as well as port. For changing host use .setHost() method

You can also use default constructor as:

Credential credential = new AuthorizationCodeInstalledApp( flow, 
             new LocalServerReceiver("Host", XXXX).authorize("user");
like image 44
Sahil Dadhich Avatar answered Sep 29 '22 20:09

Sahil Dadhich