asp.net-coremailkitoffice365-apps

How to maintain connectivity to Office365 with Mailkit without reauthenticating user each email sent


Some background information which could be helpful to other potential solutions. My organization uses my windows login to also access other software applications such as office365 (outlook online and outlook desktop app). This password is required to be updated every so often and all other applications update together all at once so they are all interconnected. I am assuming there is some identity server or something which is tying it all together for authentication. So I am building a ASP.NET Core application which a user logs in via different username and password that is stored in a SQL Server. My web application will soon require email functionality so the users can send emails after doing some function in the application. Therefore I have put together the following code from examples online which does in fact work and let me send emails through our office365 email accounts using our organization official username(assigned email address for the mailkit below) and passwords.

        public void SendEmail() 
    {
        // create email message

        var email = new MimeMessage();
        email.From.Add(MailboxAddress.Parse("myEmail@something.org"));
        email.To.Add(MailboxAddress.Parse("recipient@something.org"));
        email.Subject = "my Subject";
        email.Body = new TextPart(TextFormat.Plain) { Text = "Email sending has been integrated!" };

        //send email
        using var smtp = new SmtpClient();
        
        //office 365
        //Office 365 does not support NTML (windows authentication) so we need some sort of interation with an API to not store usernames and passwords.
        smtp.Connect("smtp.office365.com", 587, SecureSocketOptions.StartTls);
        smtp.Authenticate("myEmail@something.org", "passwordDuh");

        smtp.Send(email);
        smtp.Disconnect(true);
    }

Since my web application does not connect to this identity server for authentication (really do not have much information on this internal service for my organization and my web application is more of a side project for my team) I have to manually insert the username and password into the code above. For testing purposes I have just added them in but I need to automate this or improve the code to be up to best practices. Therefore, I have thought of the following solutions but I am not sure of the feasibility of some of them or if there is a better/more appropriate solution:

  1. Forgo my current username and password setup in the SQL DB and authenticate users at login with this identity server indirectly by using the current line below for mailkit and use in my login class to check the credentials against office365 (which probably uses our organizations identity server since the passwords are all linked with it):

    smtp.Authenticate("myEmail@something.org", "passwordDuh")

And if it succeeds to authenticate then I know the office365 credentials are correct and I will create my authentication token as usual that I send for the users frontend to log into the site and stay logged in. My issue with this is I am not sure how I could stay connected to office365 so that I could send emails later on in the application use. It is unclear if this connection and authentication technique expires after some time if the user hasnt sent an email for an hour or so.

  1. Use a similar approach to 1 but if I cannot keep the connection open I can take the user password if authenticated with office365 and encrypt the password with a salt then store in session a session variable. From everything I have read on stack answers seem to advice against storing passwords in plaintext in session and say to encrypt it but the responses tend to also give the sense this is still not adviced to keep even an encrypted password in session.

  2. Figure out some way to get a token from office365 when a user logs into my site and authenticates with office365. Reading some solutions regarding getting an access token from office365 seems to indicate you need to have the application registered in azure and approved by an admin. This seems more like an api token to access their api for my application itself and not using a users username and password. I am less familiar with this solution so forgive me for my ignorance but I have tried researching this quite a bit and seems to not lead me to anything I can use unless I am missing something.


Solution

  • My organization uses my windows login to also access other software applications such as office365 (outlook online and outlook desktop app).

    Oof. That's not a good practice.

    1. Forgo my current username and password setup in the SQL DB and authenticate users at login with this identity server indirectly by using the current line below for mailkit and use in my login class to check the credentials against office365 (which probably uses our organizations identity server since the passwords are all linked with it):

    I'm not sure I understand how this would work. Are you saying that when a user connects to your web service, they would have to provide their login credentials that you would then send to office365 to test to see if authentication is successful?

    >_<

    That seems like a hack that I doubt security experts would approve of since it requires passing along naked passwords.

    A better approach (although I am no security expert, so take this with a grain of salt) might be to use an OAuth2 code flow to authenticate with office365 which would give you an access token that could then be used later for sending mails via MailKit's SmtpClient.

    My issue with this is I am not sure how I could stay connected to office365 so that I could send emails later on in the application use. It is unclear if this connection and authentication technique expires after some time if the user hasnt sent an email for an hour or so.

    You'd have to spawn a thread or something that would periodically call SmtpClient's NoOp() method (or NoOpAsync()). to keep the connection alive, although even this could eventually fail to keep that connection open eventually, so you'd have to be able to deal with a situation where the connection gets dropped.

    1. Use a similar approach to 1 but if I cannot keep the connection open I can take the user password if authenticated with office365 and encrypt the password with a salt then store in session a session variable. From everything I have read on stack answers seem to advice against storing passwords in plaintext in session and say to encrypt it but the responses tend to also give the sense this is still not adviced to keep even an encrypted password in session.

    Yea, not a great approach. You'd obviously still have to have a way to decrypt the password again and therein lies the weakness.

    1. Figure out some way to get a token from office365 when a user logs into my site and authenticates with office365. Reading some solutions regarding getting an access token from office365 seems to indicate you need to have the application registered in azure and approved by an admin. This seems more like an api token to access their api for my application itself and not using a users username and password. I am less familiar with this solution so forgive me for my ignorance but I have tried researching this quite a bit and seems to not lead me to anything I can use unless I am missing something.

    They are talking about an OAuth2 access token and this is most likely the correct way to go.

    I wrote up some documentation on how to do this with a native desktop app, but I never got around to documenting this for an ASP.NET (or ASP.NET Core) web app. You can find the docs for the desktop app here: https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md

    Someone recently submitted ASP.NET web app docs for similar GMail authentication which can be found here: https://github.com/jstedfast/MailKit/blob/master/GMailOAuth2.md#authenticating-an-aspnet-web-app-with-the-oauth2-client-id-and-secret

    You'll likely need to read the docs and find some samples for ASP.NET Core usage, but it's possible to do.