I’m trying to build an email client that connects to Gmail using IMAP with OAuth2 authentication but I keep getting authentication failures.
I can get the access token just fine, but when I try to connect to Gmail’s IMAP server, it always says “Invalid credentials”. Here’s what I did:
- Set up OAuth2 credentials in Google Console
- Got authorization code through browser
- Exchanged code for access and refresh tokens
- Used access token in my IMAP connection
My account has 2FA enabled and I created an app-specific password too.
public class GmailSaslFactory implements SaslClientFactory {
private static final Logger log = Logger.getLogger(GmailSaslFactory.class.getName());
public static final String TOKEN_PROPERTY = "mail.imaps.sasl.mechanisms.oauth2.token";
public SaslClient createSaslClient(String[] mechanisms, String authId,
String protocol, String server,
Map<String, ?> properties, CallbackHandler handler) {
boolean found = false;
for (String mech : mechanisms) {
if ("XOAUTH2".equalsIgnoreCase(mech)) {
found = true;
break;
}
}
if (!found) {
log.info("No matching mechanisms found");
return null;
}
return new GmailSaslClient((String) properties.get(TOKEN_PROPERTY), handler);
}
public String[] getMechanismNames(Map<String, ?> properties) {
return new String[] {"XOAUTH2"};
}
}
class GmailSaslClient implements SaslClient {
private final String accessToken;
private final CallbackHandler handler;
private boolean finished = false;
public GmailSaslClient(String token, CallbackHandler cbHandler) {
this.accessToken = token;
this.handler = cbHandler;
}
public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
if (finished) {
return new byte[] {};
}
NameCallback nameCallback = new NameCallback("username");
PasswordCallback passCallback = new PasswordCallback("pass", false);
try {
handler.handle(new Callback[] { nameCallback, passCallback });
} catch (Exception e) {
throw new SaslException("Callback failed: " + e);
}
String username = nameCallback.getName();
String authString = String.format("user=%s\1auth=Bearer %s\1\1",
username, accessToken);
finished = true;
return authString.getBytes();
}
public boolean isComplete() {
return finished;
}
}
public void connectToGmail(String[] arguments) throws Exception {
String userEmail = arguments[0];
String token = arguments[1];
Security.addProvider(new OAuth2Provider());
Properties config = new Properties();
config.put("mail.imaps.sasl.enable", "true");
config.put("mail.debug.auth", "true");
config.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
config.put(GmailSaslFactory.TOKEN_PROPERTY, token);
Session mailSession = Session.getInstance(config);
mailSession.setDebug(true);
IMAPSSLStore imapStore = new IMAPSSLStore(mailSession, null);
imapStore.connect("imap.gmail.com", 993, userEmail, "");
}
The debug output shows:
DEBUG IMAPS: SASL client XOAUTH2
B1 AUTHENTICATE XOAUTH2
B1 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)
What am I doing wrong here? The access token works fine when I test it with Google’s APIs directly.