javagoogle-oauthemail-clientgmail-imap

Converting desktop Java gmail access app from password to OAuth2: AUTHENTICATIONFAILED


I have been using a desktop Java application (DigitGrabber) to peek into my email server (that's hosted by Google) to quickly ferret out the second factor six digit codes that so many sites are using these days.

This worked great until Google decided to force me to turn on OAuth2. I configured my regular email client (Thunderbird) for OAuth fine, but I'm having trouble getting my DigitGrabber working. I'm getting:

jakarta.mail.AuthenticationFailedException [AUTHENTICATIONFAILED] Invalid credentials (Failure)

I have gone through and registered my application with Google, downloaded the client secret into a json file, got the code working that gets the access token:

String accessToken = Main.authorize(userId).getAccessToken();

Main.authorize().getAccessToken() seems to be working. At least I get an access token back, but when I try to get to my inbox, it fails with the authentication issue.

QUESTION: What is wrong with my approach or code that is causing the authentication failure? Is there some application "alignment issue" that I need to address? All I did was go to https://console.cloud.google.com and register and it gave me an application name, a client secret, etc, that I've been using with all my test programs. And I've just been fumbling around, grabbing code from here and there to get to the point I'm at now, not really familiar with the Google cloud landscape. Is there somewhere in my code where I can log some details of some objects that might shed light on why I'm not getting authenticated?

Also, I'm getting a warning that I don't understand:

Apr 23, 2025 11:51:27 AM com.google.api.client.util.store.FileDataStoreFactory setPermissionsToOwnerOnly
WARNING: unable to change permissions for everybody: C:\Users\Owner\.credentials\google-oauth

Below is the code I'm running:

private String runImap(String userId) {
    Session session = null;
    Store store = null;
    Folder folder = null;
    try {
        boolean useOauth = true;
        //https://kgiann78.github.io/java/gmail/2017/03/16/JavaMail-send-mail-at-Google-with-XOAUTH2/
        //https://learn.microsoft.com/en-us/answers/questions/1195069/javax-mail-authenticationfailedexception-authentic
        if (useOauth) {
            Properties props = System.getProperties();
            props.put("mail.store.protocol", "imap");
            props.put("mail.imap.ssl.enable", "true"); // required for Gmail
            props.put("mail.imap.starttls.enable", "true");
            props.put("mail.debug", "true");
            props.put("mail.debug.auth", "true");
            props.put("mail.imap.auth.mechanisms", "XOAUTH2");
            props.put("mail.imap.sasl.enable", "true");
            props.put("mail.imap.sasl.mechanisms", "XOAUTH2");
            props.put("mail.imap.auth.login.disable", "true");
            props.put("mail.imap.auth.plain.disable", "true");
            props.put("mail.imap.user", userId);
            session = Session.getInstance(props);
            session.setDebug(true);
            store = session.getStore("imap");
            String accessToken = Main.authorize(userId).getAccessToken();
            System.out.println("access token [" + accessToken + "]"); // I DO get an access token here!!
            store.connect("imap.gmail.com", userId, accessToken); //<<<<<<<<Exception thrown here
        } else { 
            // OLD WAY
            Properties props = System.getProperties();
            props.setProperty("mail.store.protocol", "imaps");
            session = Session.getDefaultInstance(props, null);
            store = session.getStore("imaps");
            System.out.println("connecting to " + userId  + " inbox.");
            store.connect("imap.googlemail.com", userId, password);
        }
        
        folder = store.getFolder("inbox");
        folder.open(Folder.READ_ONLY);
        Message[] msgs = folder.getMessages();
        System.out.println(msgs.length + " messages found.");
        for (int i = msgs.length - 1; i > -1; i--) {
            String code = getCodeFromMessage(msgs[i], i);
            System.out.println("code [" + code + "]");
            if (code !=null && !"******".equals(code)) break;
        }
    } catch (Exception e) {
        System.err.println("SimpleGUI.actionPerformed() runImap(): " + e.getClass().getName() + " " + e.getMessage());
        e.printStackTrace();
        return e.getMessage();
    } finally {
        try {folder.close(false); System.out.println("Folder closed."); /*expunge false means do not delete*/} catch (Throwable t) {System.out.println("Failed to close folder.");} 
        try {store.close(); System.out.println("Store closed."); } catch (Throwable t) {} // ignore
    }
    return null; //good result if we didn't return from the catch
}


public class Main {
private static final String CLIENT_SECRET_FILENAME = "C:\\Users\\Owner\\eclipse-workspace\\GetEmailCodes\\client_secret_gibberishhere.json";
private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), ".credentials/google-oauth");
private static FileDataStoreFactory dataStoreFactory;
private static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static NetHttpTransport httpTransport;
private static final List<String> SCOPES = Arrays.asList("https://mail.google.com", "https://www.googleapis.com/auth/userinfo.profile","https://www.googleapis.com/auth/userinfo.email");


static {
    try {
        httpTransport = new NetHttpTransport();
        dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static Credential authorize(String userId) throws Exception {
    // Load client secrets.
    System.out.println("Can read " + CLIENT_SECRET_FILENAME + ": " + new File(CLIENT_SECRET_FILENAME).canRead());
    
    InputStreamReader isr = new InputStreamReader(new FileInputStream(CLIENT_SECRET_FILENAME));
    
    System.out.println("InputStream created: " + isr);
    System.out.println("InputStream ready " + isr.ready());
    
    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, isr);
    
    System.out.println("clientSecrets " + clientSecrets);
    
    if (clientSecrets == null) {
        System.out.println("can read " + CLIENT_SECRET_FILENAME + ": " + new File(CLIENT_SECRET_FILENAME).canRead());
        throw new Exception("clientSecrets was null");
    }

    // Build flow and trigger user authorization request.
    GoogleAuthorizationCodeFlow flow =
            new GoogleAuthorizationCodeFlow.Builder(
                    httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
                    .setDataStoreFactory(dataStoreFactory)
                    .setAccessType("offline")
                    .build();
    LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
    Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize(userId);
    
    long expiresInSeconds = credential.getExpiresInSeconds();
    System.out.println("Expires in seconds: " + expiresInSeconds);
    
    if (expiresInSeconds < 60) {
        boolean refreshResult = credential.refreshToken();
        System.out.println("refresh token result " + refreshResult);
        expiresInSeconds = credential.getExpiresInSeconds();
        System.out.println("Expires in seconds: " + expiresInSeconds);
        
    }
    
    
    return credential;
}
} 

The following is the output I'm getting:

checking email for someemail@somedomain.com
DEBUG: Jakarta Mail version 2.1.3
DEBUG: URL jar:file:/C:/Users/Owner/.m2/repository/org/eclipse/angus/angus-mail/2.0.3/angus-mail-2.0.3.jar!/META-INF/javamail.providers
DEBUG: successfully loaded resource: jar:file:/C:/Users/Owner/.m2/repository/org/eclipse/angus/angus-mail/2.0.3/angus-mail-2.0.3.jar!/META-INF/javamail.providers
DEBUG: successfully loaded resource: /META-INF/javamail.default.providers
DEBUG: Tables of loaded providers
DEBUG: Providers Listed By Class Name: {org.eclipse.angus.mail.imap.IMAPStore=jakarta.mail.Provider[STORE,imap,org.eclipse.angus.mail.imap.IMAPStore,Oracle], org.eclipse.angus.mail.smtp.SMTPTransport=jakarta.mail.Provider[TRANSPORT,smtp,org.eclipse.angus.mail.smtp.SMTPTransport,Oracle], org.eclipse.angus.mail.pop3.POP3Store=jakarta.mail.Provider[STORE,pop3,org.eclipse.angus.mail.pop3.POP3Store,Oracle], org.eclipse.angus.mail.pop3.POP3SSLStore=jakarta.mail.Provider[STORE,pop3s,org.eclipse.angus.mail.pop3.POP3SSLStore,Oracle], org.eclipse.angus.mail.smtp.SMTPSSLTransport=jakarta.mail.Provider[TRANSPORT,smtps,org.eclipse.angus.mail.smtp.SMTPSSLTransport,Oracle], org.eclipse.angus.mail.imap.IMAPSSLStore=jakarta.mail.Provider[STORE,imaps,org.eclipse.angus.mail.imap.IMAPSSLStore,Oracle]}
DEBUG: Providers Listed By Protocol: {imap=jakarta.mail.Provider[STORE,imap,org.eclipse.angus.mail.imap.IMAPStore,Oracle], smtp=jakarta.mail.Provider[TRANSPORT,smtp,org.eclipse.angus.mail.smtp.SMTPTransport,Oracle], pop3=jakarta.mail.Provider[STORE,pop3,org.eclipse.angus.mail.pop3.POP3Store,Oracle], imaps=jakarta.mail.Provider[STORE,imaps,org.eclipse.angus.mail.imap.IMAPSSLStore,Oracle], smtps=jakarta.mail.Provider[TRANSPORT,smtps,org.eclipse.angus.mail.smtp.SMTPSSLTransport,Oracle], pop3s=jakarta.mail.Provider[STORE,pop3s,org.eclipse.angus.mail.pop3.POP3SSLStore,Oracle]}
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: URL jar:file:/C:/Users/Owner/.m2/repository/org/eclipse/angus/angus-mail/2.0.3/angus-mail-2.0.3.jar!/META-INF/javamail.address.map
DEBUG: successfully loaded resource: jar:file:/C:/Users/Owner/.m2/repository/org/eclipse/angus/angus-mail/2.0.3/angus-mail-2.0.3.jar!/META-INF/javamail.address.map
DEBUG: setDebug: Jakarta Mail version 2.1.3
DEBUG: getProvider() returning jakarta.mail.Provider[STORE,imap,org.eclipse.angus.mail.imap.IMAPStore,Oracle]
DEBUG IMAP: mail.imap.fetchsize: 16384
DEBUG IMAP: mail.imap.ignorebodystructuresize: false
DEBUG IMAP: mail.imap.statuscachetimeout: 1000
DEBUG IMAP: mail.imap.appendbuffersize: -1
DEBUG IMAP: mail.imap.minidletime: 10
DEBUG IMAP: enable STARTTLS
DEBUG IMAP: enable SASL
DEBUG IMAP: SASL mechanisms allowed: XOAUTH2
DEBUG IMAP: closeFoldersOnStoreFailure
Apr 23, 2025 8:18:37 PM com.google.api.client.util.store.FileDataStoreFactory setPermissionsToOwnerOnly
WARNING: unable to change permissions for everybody: C:\Users\Owner\.credentials\google-oauth
Apr 23, 2025 8:18:37 PM com.google.api.client.util.store.FileDataStoreFactory setPermissionsToOwnerOnly
WARNING: unable to change permissions for owner: C:\Users\Owner\.credentials\google-oauth
Can read C:\Users\Owner\eclipse-workspace\EmailCodesOauth\src\main\resources\client_secret_947814295630-3n(removed some gibberish)ko.apps.googleusercontent.com.json: true
InputStream created: java.io.InputStreamReader@256f825c
InputStream ready true
clientSecrets {"installed":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_id":"947814295630-3nc1(removed some gibberish)1ko.apps.googleusercontent.com","client_secret":"GOCSPX-E66i(removed some gibberish)mTNl","redirect_uris":["http://localhost"],"token_uri":"https://oauth2.googleapis.com/token","project_id":"getemailcodes-45(removed some numbers)","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}
Expires in seconds: 3537
access token [ya29.a0AZYkNZ(removed some gibberish)g_N7HdPfGlTw0175]
DEBUG IMAP: trying to connect to host "imap.gmail.com", port 993, isSSL true
* OK Gimap ready for requests from 136.000.0000.155 (put 000 twice) tz17(removed some gibberish)qkn
A0 CAPABILITY
* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER
A0 OK Thats all she wrote! tz17(removed some gibberish)kn
DEBUG IMAP: AUTH: XOAUTH2
DEBUG IMAP: AUTH: PLAIN
DEBUG IMAP: AUTH: PLAIN-CLIENTTOKEN
DEBUG IMAP: AUTH: OAUTHBEARER
DEBUG IMAP: protocolConnect login, host=imap.gmail.com, user=someemail@somedomain.com, password=<non-null>
DEBUG IMAP: SASL Mechanisms:
DEBUG IMAP:  XOAUTH2
DEBUG IMAP: 
DEBUG IMAP: SASL client XOAUTH2
DEBUG IMAP: SASL callback length: 2
DEBUG IMAP: SASL callback 0: javax.security.auth.callback.NameCallback@7e5fa540
DEBUG IMAP: SASL callback 1: javax.security.auth.callback.PasswordCallback@1c9c1c10
A1 AUTHENTICATE XOAUTH2 dXNlcj1kYWxlQH(removed some gibberish)kdsVHcwMTc1AQE=
+ eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoi(removed some gibberish)Z29vZ2xlLmNvbS8ifQ==
DEBUG IMAP: SASL no response

A1 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)
SimpleGUI.actionPerformed() runImap(): jakarta.mail.AuthenticationFailedException [AUTHENTICATIONFAILED] Invalid credentials (Failure)
jakarta.mail.AuthenticationFailedException: [AUTHENTICATIONFAILED] Invalid credentials (Failure)
    at org.eclipse.angus.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:724)
    at jakarta.mail.Service.connect(Service.java:345)
    at jakarta.mail.Service.connect(Service.java:225)
    at com.sengsational.eco.SimpleGUI.runImap(SimpleGUI.java:154)
    at com.sengsational.eco.SimpleGUI.actionPerformed(SimpleGUI.java:116)
    at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
    at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2313)
    at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
    at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
    at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
    at java.desktop/java.awt.Component.processMouseEvent(Component.java:6620)
    at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3398)
    at java.desktop/java.awt.Component.processEvent(Component.java:6385)
    at java.desktop/java.awt.Container.processEvent(Container.java:2266)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4995)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4827)
    at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
    at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
    at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
    at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4827)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Failed to close folder.
DEBUG IMAP: IMAPStore cleanup, not connected
Store closed.

Solution

  • The problem, in my case, was that somehow the data in C:\Users\Owner\.credentials\google-oauth\StoredCredential was wrong.

    I ran the following code, then tried again. It opened the browser, I typed the userId and password, closed the browser, and it was able to access my email account through IMAP.

    The code in the original question is probably full of unneeded things due to my trial and error, and isn't exactly what I've got working (but it's close).

            File aFile = new File(System.getProperty("user.home"), ".credentials/google-oauth");
            System.out.println("file [" + aFile.getAbsolutePath() + "]");
            FileDataStoreFactory dataStoreFactory = new FileDataStoreFactory(aFile);
            DataStore dataStore = dataStoreFactory.getDataStore("StoredCredential");
            System.out.println("dataStore " + dataStore);
            dataStore.delete("myemail@gmail.com");