How to use this for authenticating an SMTP session?
tks-socius opened this issue · 2 comments
I have used this app (downloaded from the AAD portal with my secret in it) and tried to use the given token for SMTP authentication, using the information from
- https://support.microsoft.com/en-gb/office/pop-imap-and-smtp-settings-8361e398-8af4-4e97-b147-6c6c4ac95353?ui=en-us&rs=en-gb&ad=gb
- https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
Logging in with my office email account works and the /graphcall
works as well. However the SMTP authentication is not working, I am receiving reply: retcode (535); Msg: b'5.7.3 Authentication unsuccessful [LO2P265CA0061.GBRP265.PROD.OUTLOOK.COM]'
from it.
The Scope of the token that I am asking for is SCOPE = ["User.ReadBasic.All", "https://outlook.office.com/SMTP.Send"]
and the app has SMTP.send
turned on in the config file
Here is a class extending smtplib.SMTP
that I have written based on those:
import smtplib
import base64
class MicrosoftSMTP(smtplib.SMTP):
def __init__(self, host="smtp.office365.com", port=587, **kwargs):
super().__init__(host=host, port=port, **kwargs)
@staticmethod
def encode_auth_token(username, token):
just_a_str = f"user={username}\x01auth=Bearer {token}\x01\x01"
xoauth2_token = base64.b64encode(just_a_str.encode())
return xoauth2_token
def authenticate(self, username, token):
self.helo()
# first step, we
code, msg = self.docmd("auth", "XOAUTH2")
if code != 334:
raise Exception(msg.decode())
# send the token
self.send(self.encode_auth_token(username, token))
and the code to connect with the credentials from the app here, and adding a page where I display the token json for a sanity check:
@app.route("/send_to_self")
def send_to_self():
token = _get_token_from_cache(app_config.SCOPE)
if not token:
return redirect(url_for("login"))
# connect to the server
connection = MicrosoftSMTP()
connection.set_debuglevel(True) # for output
connection.starttls()
connection.authenticate(
# same as session["user"]["preferred_username"]
token["id_token_claims"]["preferred_username"],
token["access_token"],
)
# ... would write an email here with connection.sendmail( ... )
connection.quit()
return render_template(
"send_to_self.html",
data=token,
data_session=session["flow"],
data_user=session["user"],
)
The authentication is failing, here is the full log:
send: 'ehlo 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa\r\n'
reply: b'250-LO2P265CA0516.outlook.office365.com Hello [<A.GENERAL.IP>]\r\n'
reply: b'250-SIZE 157286400\r\n'
reply: b'250-PIPELINING\r\n'
reply: b'250-DSN\r\n'
reply: b'250-ENHANCEDSTATUSCODES\r\n'
reply: b'250-STARTTLS\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250-BINARYMIME\r\n'
reply: b'250-CHUNKING\r\n'
reply: b'250 SMTPUTF8\r\n'
reply: retcode (250); Msg: b'LO2P265CA0516.outlook.office365.com Hello [<A.GENERAL.IP>]\nSIZE 157286400\nPIPELINING\nDSN\nENHANCEDSTATUSCODES\nSTARTTLS\n8BITMIME\nBINARYMIME\nCHUNKING\nSMTPUTF8'
send: 'STARTTLS\r\n'
reply: b'220 2.0.0 SMTP server ready\r\n'
reply: retcode (220); Msg: b'2.0.0 SMTP server ready'
send: 'helo 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa\r\n'
reply: b'250 LO2P265CA0516.outlook.office365.com Hello [<A.GENERAL.IP>]\r\n'
reply: retcode (250); Msg: b'LO2P265CA0516.outlook.office365.com Hello [<A.GENERAL.IP>]'
send: 'auth XOAUTH2\r\n'
reply: b'334 \r\n'
reply: retcode (334); Msg: b''
send: b'dX......EB'
send: 'quit\r\n'
reply: b'535 5.7.3 Authentication unsuccessful [LO2P265CA0516.GBRP265.PROD.OUTLOOK.COM]\r\n'
reply: retcode (535); Msg: b'5.7.3 Authentication unsuccessful [LO2P265CA0516.GBRP265.PROD.OUTLOOK.COM]'
things I have cheeked:
- we have SMTP allowed for this mailbox
- the token has the SMTP allowed
- the XOAUTH2 token encoder's output matches that of the example on the website
FYI the token data looks like this, with the tokens and username removed
{
"access_token": "ey<...>aw",
"client_info": "ey<...>In0",
"expires_in": 3599,
"ext_expires_in": 3599,
"id_token": "ey<...>jQ",
"id_token_claims": {
"aud": "8<...>9",
"exp": 1634319637,
"iat": 1634315737,
"iss": "https://login.microsoftonline.com/5<...>1/v2.0",
"name": "<Name of the user>",
"nbf": 1634315737,
"nonce": "c1<...>d0",
"oid": "cd<...>1b",
"preferred_username": "user.name@company.com",
"rh": "0.A<...>As.",
"sub": "2w<...>ww",
"tid": "50<...>31",
"uti": "8W<...>AA",
"ver": "2.0"
},
"refresh_token": "0.A<...>4Y",
"scope": "openid profile SMTP.Send User.ReadBasic.All email",
"token_type": "Bearer"
}
I haven't tried using the acquired access token (AT) in an SMTP session. Would you mind checking out from the SMTP side of docs to see what credential does SMTP accept? Traditionally, SMTP session would require the end user password, and a send mail Graph API would require an OAuth2 access token. They are different, unless an SMTP service explicitly documents that you could use an AT as SMTP password.
This sample utilizes the MSAL library to get you an AT, and AT only.
Besides the comment above, it seems the StackOverflow community answered your question since then. Please follow up there.