Skip to main content

Using 2 Authentication factors (for MFA) in an unattended PowerShell Script

MFA (Multi Factor Authentication) is great at making the Authentication process more secure in Exchange Online but can be challenging in Automation scenarios. I originally wrote this code for something that I wanted to run unattended on a RasberryPi that was running PowerShell that i wanted to use MFA on and where i wanted to avoid going down the path of using the 90 day RefreshToken/device code method and I also didn't want to use App Authentication via Certificates or Client Secrets.

Interestingly while i was writing this post Microsoft just announced Certificate based Modern Auth in Exchange Online PowerShell  .  This article also links to the Secure App Model which is the way Microsoft are recommending you handle MFA in unattended delegate scenarios so this is an alternative to this if you can't go down that path .

One thing to note about any unattended method is that by there nature (eg because they aren't interactive) they can all potentially be unraveled by a bad actor if they make it passed the outer security of the machine where the script is running. Eg an unlocked machine or week logon security to that machine that would allow an attacker to access the stored RefreshToken/Certificate/SharedKey (no matter where they are stored or how they are encrypted). However the more factors you can put into your unattended process eg encrypting the stored cert/key etc the slower its going to be for anybody trying to unravel it. The gold standard would be an Secure Azure VM where the secrets themselves are in a KeyVault and use a managed identity but in the real world this is not always possible. In security terms you should always consider what the implications are when the security around your unattended process fails (and not if it fails). 


The two factors in my script that I'm going to be using is firstly a Username and Password which is really the only Primary Authentication method that currently lends itself to be used unattended (eg FIDO and OATH requires some level of interactivity). The additional second factor I'm using is TOTP (Time based One Time passwords) which are relatively easy to generate as they are based on . Other people have already created a few Powershell functions for generating TOTP's so the one I decided to use was which just requires that you pass in the SharedSecret that you get when you add a (3rd Party Authenticator)to your account in Office365 as an additional authentication method . To do this you should select to add an Authenticator application as an additional authentication method and select the "Configure app without notifications" from the screen that shows the QRCode. You then should be presented with a secret (shared)key (if it has spaces in it you will need to remove these) which you then need to feed into the Get-TimeBasedOneTimePassword cmdlet which will provide the verification code to successfully add the authenticator eg

Get-TimeBasedOneTimePassword -SharedSecret txxxxxxxxxxxx

You then need to store this sharedsecret as securely as you can as you will use this each time you want to logon to generate the TOTP factor for the authentication. A few ways of storing this is firstly use an Azure key vault, if you have a Windows machine your running it on then consider the credential store which can be used easily via another Powershell module and there's a simple demo of using it . (I would suggest you put a second layer of encryption on top of this so whatever goes into the credential store is also encrypted)

MFA Code

So far I've been using a whole bunch of other peoples code but now comes to the bit i wrote which does the MFA authentication using OpenId connect and then gets an AccessToken once an authorization_code is acquired. This code is relatively straight forward it starts the normal browser based authentication flow maintaining the session using Invoke-WebRequest's -WebSession parameter and parses out the context, flow and canary tokens from the response which are needed in various parts of the process. For the additional authentication factor the TOTP is used.


While my code was written for EWS primarily running on a Rasberry PI a more relevant sample is a Exchange Online PowerShell connection eg 

So this simple example uses the CredentialManger PS module to grab a ShareSecret from the credential store and generate a TOTP and then you have a normal set of PSCredentials and the "BasicAuthToOAuthConversion=true" query-string does the rest.

A few things to remember is that Access Tokens are only good for 1 hour so if you expect your script to run for longer then this you will need to put in a method of refreshing the Token (or just generate a new access token). 

I've put the code for the AzureMFAOTPv2 module on GitHub here


Popular posts from this blog

Export calendar Items to a CSV file using EWS and Powershell

Somebody asked about this last week and while I have a lot of EWS scripts that do access the Calendar I didn't have a simple example that just exported a list of the Calendar events with relevant information to a CSV file so here it is. I've talked on this one before in this howto  but when you query the calendar folder using EWS you need to use a CalendarView which will expand any recurring appointments in a calendar. There are some limits when you use a calendarview in that you can only return a maximum of 2 years of appointments at a time and paging will limit the max number of items to 1000 per call. So if you have a calendar with a very large number of appointments you need to break your query into small date time blocks. In this example script I'm just grabbing the next 7 days of appointments if you want to query a longer period you need to adjust the following lines (keeping in mind what I just mentioned) #Define Date to Query $StartDate = (Get-Date) $EndDate

Downloading a shared file from Onedrive for business using Powershell

I thought I'd quickly share this script I came up with to download a file that was shared using One Drive for Business (which is SharePoint under the covers) with Powershell. The following script takes a OneDrive for business URL which would look like This script is pretty simple it uses the SharePoint CSOM (Client side object Model) which it loads in the first line. It uses the URI object to separate the host and relative URL which the CSOM requires and also the SharePointOnlineCredentials object to handle the Office365 SharePoint online authentication. The following script is a function that take the OneDrive URL, Credentials for Office365 and path you want to download the file to and downloads the file. eg to run the script you would use something like ./spdownload.ps1 '

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.