I would like Nextcloud Mail client to use the SSO token to authenticate with dovecot oauth - I can successfully login to IMAP with all the same info Nextcloud already has

Environment:

  1. Keycloak hosted in k8s
  2. dovecot hosted in k8s, successfully authenticating to keycloak oidc and supporting “xoauth2” IMAP CAPABILITY
  3. Nextcloud hosted in k8s with mail app enabled.

Problem:

  1. When setting up Nextcloud Mail app to authenticate with the xoauth2 enabled IMAP dovecot server it does not attempt oauth authentication but rather explains that I’m logged in with a passwordless account and would first need to login with a password.

Question/Recommendation:

  1. Why not allow xoauth2 authentication to any IMAP server? Dovecot would require the following IMAP commands to successfully login:
  • tag AUTHENTICATE XOAUTH2
  • { base64 encoded data }
    – The base64 encoded data is as follows:
    —echo -en “user=emailAddress\001auth=Bearer token\001\001” | base64 -w0; echo

Evidence:

  1. I use the same realm for Nextcloud and Dovecot clients in Keycloak. When I curl to acquire a new token from the Nextcloud client I assume I would then possess the same token that Nextcloud has after oidc Browser Flow login. With that token I can use the openssl s_client to successfully login to the IMAP server with XOAUTH2 capability using the Keycloak Dovecot client.

NOTES:

  1. My method for retrieving the tokens from the two clients in the same keycloak realm were as follows:

curl -sq -d ‘client_id=clientId’ -d ‘client_secret=clientSecret’ -d ‘username=emailAddress’ -d ‘password=userPassword’ -d ‘grant_type=password’ ‘https://keycloakURL/realms/eamail/protocol/openid-connect/token’ | jq -j ‘.access_token’

I would just change the client_id and client_secret to get a token from the various clients in the same realm.

  1. I am using the oidc_login app from here: OpenID Connect Login - Apps - App Store - Nextcloud
1 Like

When I capture the authentication from Nextcloud to Keycloak I end up with a response with the following:

  • AUTH_SESSION_ID
  • KEYCLOAK_IDENTITY
  • KEYCLOAK_IDENTITY_LEGACY
  • KEYCLOAK_SESSION
  • KEYCLOAK_SESSION_LEGACY
  • KC_RESTART

It seems Nextcloud is making use of an id_token but what I do not see is an access_token. Still researching…

It might be that the oidc_login app is requesting the claim_token_format https://openid.net/specs/openid-connect-core-1_0.html#IDToken instead of the urn:ietf:params:oauth:token-type:jwt. You can read about this in Authorization Services Guide.

Current line of thought; I’m thinking that I need to find a way to get oidc_login to use the access_token instead of the id_token. With this I would then have an access_token saved in session that I can then figure out how to pass in the mail app to the IMAP server. Here’s what I think I know so far.

I found this code which is an example of requesting a resource owner token:

use Jumbojett\OpenIDConnectClient;

$oidc = new OpenIDConnectClient('https://id.provider.com',
                                'ClientIDHere',
                                'ClientSecretHere');
$oidc->providerConfigParam(array('token_endpoint'=>'https://id.provider.com/connect/token'));
$oidc->addScope('my_scope');

//Add username and password
$oidc->addAuthParam(array('username'=>'<Username>'));
$oidc->addAuthParam(array('password'=>'<Password>'));

//Perform the auth and return the token (to validate check if the access_token property is there and a valid JWT) :
$token = $oidc->requestResourceOwnerToken(TRUE)->access_token;

in the README located at custom_apps/oidc_login/3rdparty/jumbojett/openid-connect-php/README.md under Example 5, however, I’m finding it difficult to determine if the oidc_login app is currently using this method of requesting a token. I think it is written differently somewhere in all of Nextcloud’s code.

So I still haven’t identified why Nextcloud doesn’t have the access_token.

Nextcloud has the built in Google xoauth2, why not allow it for other IMAP and SMTP servers?

Here’s a code snippet form the GoogleIntegration.php where it shows the use of grant_type authorization_code, how do I get this to work for my IMAP and SMTP server as well?

public function isGoogleOauthAccount(Account $account): bool {
		return $account->getMailAccount()->getInboundHost() === 'imap.gmail.com'
			&& $account->getMailAccount()->getAuthMethod() === 'xoauth2';
	}

	public function finishConnect(Account $account,
								  string $code): Account {
		$clientId = $this->config->getAppValue(Application::APP_ID, 'google_oauth_client_id');
		$encryptedClientSecret = $this->config->getAppValue(Application::APP_ID, 'google_oauth_client_secret');
		if (empty($clientId) || empty($encryptedClientSecret)) {
			// This is highly unexpected
			$this->logger->critical('Can not finish Google account linking due to missing client secrets');
			return $account;
		}
		$clientSecret = $this->crypto->decrypt($encryptedClientSecret);
		$httpClient = $this->clientService->newClient();
		try {
			$response = $httpClient->post('https://oauth2.googleapis.com/token', [
				'content-type' => 'application/json',
				'body' => json_encode([
					'client_id' => $clientId,
					'client_secret' => $clientSecret,
					'grant_type' => 'authorization_code',
					'redirect_uri' => $this->getRedirectUrl(),
					'code' => $code,
				], JSON_THROW_ON_ERROR)
			]);
		} catch (Exception $e) {
			$this->logger->error('Could not link Google account: ' . $e->getMessage(), [
				'exception' => $e,
			]);
			return $account;
		}

		$data = json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR);
		$encryptedRefreshToken = $this->crypto->encrypt($data['refresh_token']);
		$account->getMailAccount()->setOauthRefreshToken($encryptedRefreshToken);
		$encryptedAccessToken = $this->crypto->encrypt($data['access_token']);
		$account->getMailAccount()->setOauthAccessToken($encryptedAccessToken);
		$account->getMailAccount()->setOauthTokenTtl($this->timeFactory->getTime() + $data['expires_in']);
		return $account;
	}
2 Likes

Did you make any further progress in finding a solution?
I have a similar problem and was looking into it as well.

I have not, I was hoping for someone that’s legitimately familiar with this section of the code to respond and say “Yea, that’s easy, give me 10 minutes”. My time is taken by another stage of my project so that I can continue making progress while waiting for a response on this part.

1 Like

I’m also very interested in this!

1 Like

I believe Nextcloud is VERY close to having this functionality.

  1. Maybe Nextcloud is getting an access_token but just choosing not to store it and using the id_token instead for authentication.
  2. Maybe Nextcloud is storing the access_token somewhere and it isn’t documented.

I think that once we know about the access_token it would be minor modifications to enable that token to be passed to any XOAUTH2 capable IMAP server.

1 Like