====
This is example of an OIDC setup with James.
The API Gateway for example is Apisix, we can use Apisix for websocket gateway, horizontal scaling, etc...
This docker compose will start the following services:
- apisix: The image
linagora/apisix:3.2.0-debian-javapluginwas created by Linagora. It based onapisix:3.2.0-debian, it already contain apisix plugin for SLO (Single Logout) and rewrite theX-Userheader.- Apisix being the OIDC gateway against James by exposing two endpoints:
POST /jmapfor JMAP requests against James with normal authenticationPOST /oidc/jmapfor JMAP request against James with a JWT token issued by the LemonLDAP
- Apisix being the OIDC gateway against James by exposing two endpoints:
- james: Memory version using the LDAP and
XUserAuthenticationStrategyfor JMAP - sso.example.com: keycloak image with a pre-configured
oidcrealm,oidcclient (Authorization Code Flow, should dedicate for JMAP),james-thunderbirdclient (Client Credentials Flow, dedicated for IMAP/SMTP) and connected to the LDAP for its user base - ldap: with a pre-provisioned user
james-user@tmail.comand his passwordsecret - redis: for storage of the revoked token. This is optional, we can un-use it if we want to store it in-memory with Apisix standalone.
Here is an architecture diagram showing how Single Sign On works for this example:
SSO auto-discovery might require the set up of a .well-known/webfinger endpoint described in
this spec
via external means (not provided here).
Here is an architecture diagram showing how Single Log Out works for this example, using the backchannel OIDC flow:
Here is an architecture diagram showing how to authenticate JAMES IMAP/SMTP using OIDC Provider:
docker-compose up -dBefore test it, we need to modify the /etc/hosts first.
127.0.0.1 sso.example.com apisix.example.comThere is no frontend in this example to interact directly with Keycloak and get a valid JWT token from it.
However, you can use the Keycloak playground example with the following steps (based on Authorization Code Flow):
- Open your browser and go to https://www.keycloak.org/app/
- Fill the form with your local Keycloak info and click
Save:- Keycloak URL:
http://sso.example.com:8080/auth - Realm:
oidc - Client:
oidc
- Keycloak URL:
- Click
Sign inand you will get redirected to your Keycloak login screen - Open the Developer Tools of your browser and check the network connections
- Enter the credentials of the user:
james-user@localhost / secret - Get the response of the token request and save the
access_tokensent back from Keycloak - Do a JMAP request (like a
Mailbox/get) with curl, Postman, ... towards the/oidc/jmapendpoint of Apisix:- URL:
POST http://apisix.example.com:9080/oidc/jmap - AccountId of the user (for the JMAP request body):
fe100f0103112aa50a585b7ca037c6b9387352991fc35cec15faf7ce4edd8d03 - Put the JWT token you got from Keycloak in the
Authorizationheader as a Bearer token - Don't forget the
Acceptheader as well with the valueapplication/json; jmapVersion=rfc-8621to use the JMAP spec from the RFC-8621
- URL:
If everything goes well, you should get a valid response back.
We can discover the oauth2 by endpoint http://sso.example.com:8080/auth/realms/oidc/.well-known/openid-configuration
If you want to test SSO, SLO with curl, you can use the bellow command:
Based on Client Credentials Flow
GET_TOKEN_RESPONSE=`curl --location 'http://sso.example.com:8080/auth/realms/oidc/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=openid profile email' \
--data-urlencode 'client_id=james-thunderbird' \
--data-urlencode 'client_secret=Xw9ht1veTu0Tk5sMMy03PdzY3AiFvssw' \
--data-urlencode 'username=james-user@localhost' \
--data-urlencode 'password=secret' 2>/dev/null`
ACCESS_TOKEN=`echo $GET_TOKEN_RESPONSE 2>/dev/null |perl -pe 's/^.*"access_token"\s*:\s*"(.*?)".*$/$1/'`
REFRESH_TOKEN=`echo $GET_TOKEN_RESPONSE 2>/dev/null |perl -pe 's/^.*"refresh_token"\s*:\s*"(.*?)".*$/$1/'`curl 'http://apisix.example.com:9080/oidc/jmap/session' \
--header 'Accept: application/json; jmapVersion=rfc-8621' \
--header 'Authorization: Bearer '$ACCESS_TOKEN The response example:
HTTP/1.1 200 OK
{
"capabilities": {...}
"username": "james-user@tmail.com",
"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943"
}
Logout from Keycloak
curl --location 'http://sso.example.com:8080/auth/realms/oidc/protocol/openid-connect/logout' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=james-thunderbird' \
--data-urlencode 'client_secret=Xw9ht1veTu0Tk5sMMy03PdzY3AiFvssw' \
--data-urlencode 'refresh_token='$REFRESH_TOKENVerify the revoked access token can't access the JMAP server anymore.
curl 'http://apisix.example.com:9080/oidc/jmap/session' \
--header 'Accept: application/json; jmapVersion=rfc-8621' \
--header 'Authorization: Bearer '$ACCESS_TOKEN The response example:
HTTP/1.1 401 Unauthorized
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>openresty</center>
<p><em>Powered by <a href="https://apisix.apache.org/">APISIX</a>.</em></p></body>
</html>
Use websocket with endpoint ws://apisix.example.com:9080/oidc/jmap/ws and the same access token.
We would use Thunderbird version 91.4.1 as a mail client (above versions should work).
-
Open
/thunderbird/omni.jain your host, find and modifyOAuth2Providers.jsm:- Add James hostname in kHostnames:
["localhost", ["james.example.com", "email"]], - Register using
james-thunderbirdKeycloak client in kIssuers:
[ "james.example.com", [ "james-thunderbird", //client_id from keycloak "Xw9ht1veTu0Tk5sMMy03PdzY3AiFvssw", // client_secret from keycloak "http://sso.example.com:8080/auth/realms/oidc/protocol/openid-connect/auth", "http://sso.example.com:8080/auth/realms/oidc/protocol/openid-connect/token", ], ] - Add James hostname in kHostnames:
-
Adding a line
127.0.0.1 sso.example.comto your/etc/hostsso Thunderbird can resolve the address of keycloak. -
Run Thunderbird, configure it using
james-user@localhostaccount against these IMAP/SMTP settings: -
Click
Get Messsagesin your INBOX tab, a popup will show up ask you to login against Keycloak.
After logging in succeed, you can use James IMAP/SMTP. Let try to send a mail to yourself:
Then it should work:

A remark here is that if you generate a new client_secret for james-thunderbird client in Keycloak, you have to modify
it accordingly in OAuth2Providers.jsm.
You can test logging into IMAP on the CLI by connecting with telnet localhost 143. Here are some commands that can be tried:
a AUTHENTICATE XOAUTH2 <initial response>(unauthenticated state)b AUTHENTICATE OAUTHBEARER <initial response>(unauthenticated state)c LOGOUT(any state)
You can get the initial response from the test script.
You can test logging into IMAP on the CLI by connecting with telnet localhost 4190. Here are some commands that can be tried:
AUTHENTICATE "XOAUTH2" "<initial response>"(unauthenticated state)AUTHENTICATE "OAUTHBEARER" "<initial response>"(unauthenticated state)CAPABILITY(any state)LOGOUT(any state)
You can get the initial response from the test script.
You can test logging into IMAP on the CLI by connecting with telnet localhost 587. Here are some commands that can be tried:
AUTH XOAUTH2 <initial response>(unauthenticated state)AUTH OAUTHBEARER <initial response>(unauthenticated state)QUIT(any state)
You can get the initial response from the test script.




