Apr 2025

If you happen to have an email account at Microsoft’s outlook.com service, you might have noticed that you can no longer access your inbox over IMAP. Turns out, Microsoft has disabled basic username and password authentication over IMAP some time last year and locked out all our clients from accessing our mails.

Frustratingly, the web interface on outlook.com does not have any information as to why clients might be unable to access your inbox or what you can do to fix it. It’s not that complicated, but clearer communication would have been appreciated.


Authentication Flow

Instead of using your username and password to authenticate over IMAP, you are now expected to acquire an OAUTH access token and use the XOAUTH2 authentication scheme with Microsoft’s IMAP server. If you don’t know what that means, don’t worry, I don’t either, but that’s not a problem. What is a problem is that the outlook.com web interface gives you no option to create such a token. Instead you are expected to complete one of OAUTH’s “authentication flows” to obtain one and renew it every time you want to access your email.

The authentication flow I chose to use for this (apparently there are others, but I haven’t bothered to figure out the differences) is the so-called “device code” flow and Microsoft describes it here. It works like this:

  1. You send an HTTP request to some Microsoft-owned endpoint, providing only the “scope” and a “client id” (more on these later).

  2. Microsoft sends back a “device code” and a shorter “user code”.

  3. You open up “microsoft.com/devicecode” in your web browser, enter the user code (yes, the URL says devicecode, but you enter the user code), then your username and password, and then confirm that you wish to grant the “client” permission to do things that are specified in the “scope”.

  4. You send another HTTP request, providing the device code, to ask for an access token. If step 3 was completed successfully, you will get one, if not then you have to send another request after step 3 was completed.

  5. Microsoft sends back an access token and a refresh token. The access token is only valid for a short period of time, but you can ask for a new one by providing the refresh token.

Now that you have an access token, you can authenticate to outlook.com over IMAP. (Well, technically you need to put the access token in a JSON object, but that’s basically it).


Implementation

Let’s go over this in a bit more detail.

To initiate the flow you send a request to https://login.microsoftonline.com/common/oauth2/v2.0/devicecode and give it a client id and a scope. The client id is something you get by registering your application with Microsoft. Not sure, what the point of this is since they surely don’t expect individual users to do this, but without it you can’t log in to your email account, so … weird. I didn’t bother to find out how that app registration works, I simply grabbed an existing client id from someone else’s application that I found on the internet. Unless that someone doesn’t somehow unregister their app, that should be fine (and this whole thing breaking is probably more likely to happen because of Microsoft changing something on their end).

The scope is meant to determine what a user can do with the access token that we are about to acquire. I specify these strings (all as one value to the “scope” key):

That last one apparently enables the ability to refresh the token after it has expired. Otherwise, we would need to sign in through our web browser every time we wanted to connect via IMAP, which would be extremely annoying.

So, we send these values to the url mentioned above:

URL='https://login.microsoftonline.com/common/oauth2/v2.0/devicecode'
CLIENT='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
SCOPE='https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access'
curl -F client_id="$CLIENT" -F scope="$SCOPE" -X POST "$URL"

We receive back a response with a JSON object containing (among others) the keys user_code and device_code. We use the user code and our normal login credentials to sign in to microsoft.com/devicecode through a web browser as explained above.

After we have logged in and approved the client’s request for access, we can close the browser window (there is nothing left to be done there).
Note that Microsoft doesn’t just give you the token in the browser, you have to ask for it. Now with the device_code we received earlier, we poll another Microsoft OAUTH endpoint:

URL='https://login.microsoftonline.com/common/oauth2/v2.0/token'
CLIENT='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
DEVICE='...' # the code we received through the first request
GRANT='urn:ietf:params:oauth:grant-type:device_code'
curl -F client_id="$CLIENT" -F device_code="$DEVICE" -F grant_type="$GRANT" -X POST "$URL"

The grant type is some specially formatted string, OAUTH probably knows many other types, but we don’t care.

The response is another JSON object containing the fields access_token and refresh_token.


Refreshing the token

The response you get from the “token” endpoint contains an expires_in field that currently seems to be set to 3600 seconds. Using the refresh token you can get another access token simply by doing:

URL='https://login.microsoftonline.com/common/oauth2/v2.0/token'
CLIENT='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
REFRESH='...' # the refresh token we got
GRANT='refresh_token'
curl -F client_id="$CLIENT" -F refresh_token="$REFRESH" -F grant_type="$GRANT" -X POST "$URL"

Yes, here the grant type does not have some crazy prefix, super consistent…


Using the token

If you want to use the token to access your mail, you need to craft a special string with the username and token, and base64-encode it like this:

TOKEN='...' # the access token we acquired above
USER='...'  # the username for which we got our token
BLOB=$(printf "user=$USER\x01auth=Bearer $TOKEN\x01\x01' "$TOKEN" | 
base64 -w0)

Then you can use use that blob to authenticate, which you can verify e.g. interactively through a TLS connection to the IMAP server:

openssl s_client -crlf -connect outlook.office365.com:993

Once the connection is up you can authenticate using

T0 AUTHENTICATE XOAUTH2 [BLOB]

where [BLOB] should be the blob described above. If everything worked out correctly, the server will respond with

T0 OK AUTHENTICATE completed.

Obviously most people wouldn’t want to interact with an IMAP server like this. The IMAP client I am currently using knows about the OAUTH authentication scheme and can use the appropriate method if I tell it to. What it can’t do is get the access token itself, which is why all of the previous steps were necessary.