Exchange Online CBA One Way to Replace Basic Authentication
The writing team for Office 365 for IT Pros very much appreciate the work done by our redoubtable technical editor, Vasil Michev, when he probes and questions text in the book chapters. It’s nice to get some of our own back when Vasil commits himself to print.
Take his recent article on certificate-based authentication for Exchange Online PowerShell. There’s a lot of good stuff here, but Vasil’s mind runs at such a fast rate that he sometimes omits details when he explains something that end up stopping people being able to master a topic. This is when people with slower CPUs, like me, step in to ask irritating questions. In this instance, the topic is important. Microsoft will remove support for basic authentication for PowerShell in mid-2021. Any batch job that uses PowerShell to interact with Exchange Online needs to be upgraded before this point to keep working.
Three Steps to Certificate-Based Authentication
The solution is to move to the Exchange Online Management module (known as EXO V2) and use certificate-based authentication. Microsoft has a preview of the module available to allow tenants to test how this works. The mechanism might seem complex when you first read Microsoft’s instructions, but it can be boiled down to three points:
An app registered with Azure AD. The app is the entry point to Exchange Online PowerShell because it is granted permission (as a service principal) to perform administrative actions. You might have used apps registered with Azure AD to interact with the Microsoft Graph.
A self-signed X.509 certificate used to authenticate the application against Azure AD when access is needed.
Office 365 is a massive multi-tenant environment, so you need to tell Exchange Online which tenant to connect to be able to run PowerShell in a background process.
Creating the App and Assigning Permissions and Role
Creating the app and assigning the necessary Exchange.AsManageApp permission and administrative role is quickly done in the Azure AD portal using the steps outlined in Vasil’s article. These are one-time operations that don’t need to be automated in code. However, it’s worth noting that role assignments can be made with PowerShell. It’s useful to know how to do this because you might use the technique in future to assign a role to a user.
This code fetches the object identifier for the app (it’s called “Exo Background process”) and assigns the Exchange Service Administrator directory role to the app.
The same approach is used to assign a role to a user. The difference is that the object identifier for the user is fetched with the Get-AzureUserAD cmdlet. This example shows how to assign the Global Reader role to a user.
With that diversion taken care of, we can proceed to obtaining a self-signed X.509 certificate, which is where people sometimes become stuck (I did).
Creating the Certificate
You need to upload a suitable X.509 self-signed certificate to the Azure AD app to create an association between the two. Certificates are not the easiest of objects to work with, but in this case it’s straightforward.
If you don’t have a suitable certificate to hand, you must generate one. Vasil was short on detail on this point (until I asked him how he had generated a certificate). Microsoft recommends using either the Create-SelfSignedCertificate.ps1 script or the MakeCert command-line utility. These are certainly viable options, but the easiest way is to run the New-SelfSignedCertificate cmdlet using a command like this:
This command creates a certificate valid for a year in the personal store of the user, which is fine for testing (Figure 1). Obviously, in a production environment, you’d create the certificate in the personal store of the account that will be used to run batch jobs.
Figure 1: A self-signed certificate created with the New-SelfSignedCertificate cmdlet
To associate the certificate with the app, export the certificate as a DER-encoded binary X.509 file (Figure 2). You can call the file anything you like, but you should give it a .CER extension.
Figure 2: Exporting the self-signed certificate
Finally, upload the exported file to the app in the Azure AD portal. This will generate a thumbprint that you need to note (Figure 3).
Figure 3: Importing the self-signed certificate into the registered Azure AD app
Bringing it Together with Exchange Online CBA
After setting up the app, the three vital pieces of information we need to connect are:
The AppId of the application you created.
The thumbprint of the certificate loaded into the app.
The service domain for your tenant (like tenant.onmicrosoft.com).
With these values, you can connect to Exchange Online using certificate-based authentication with a command like:
There are obviously more complications that await the unwary along the way, but this is enough to connect and play with Exchange Online PowerShell in batch jobs. Vasil’s post contains a lot of detail and there will be more articles and guidance published as we approach the deadline for basic authentication to disappear next year, which is a good reason to subscribe to a reliable source of information like the Office 365 for IT Pros eBook.
Good catch! In your examle it would be “DispalyName -eq ‘Exo Background Process'”
Loading...
Hi Tony,
I trying to grant the Exchange.AsManageApp permission but this option seems to be missing from the Supported legacy APIs.
I did assign this role before, but seems is not be available. Has there been any changes recently with regards to this ?
Please ignore, I found out to my surprise that now you will need to search it within the “APIs my organization uses” by the name : Office 365 Exchange Online. Than, you will be able to assign the necessary permission afterwards.
I’ve been looking for this tidbit for 3 days. You have to go to the “APIs my organization uses” section and you can’t find it by searching for “Exchange” because that returns nothing for some reason. Search for “Office 365” and it’s in “Office 365 Exchange Online”. It’s no wonder this has been delayed so long.
Is there a way to specify ?proxyMethod=RPS with this connection method? In the past I’ve needed this for Set-UserPhoto to work and it seems to error out when I add the -connectionuri with RPS.
I shouldn’t think so (but haven’t tested). This is designed to work with the Exchange Online management module, not the older remote PowerShell connections.
As Navin Gupta (one of the program managers for the Exchange Online PowerShell module) notes, there is a DCR (design change request) to include Set-UserPhoto in the scope for CBA. I guess we shall have to wait for the process to work through. This isn’t uncommon because there are hundreds of cmdlets in the EXO set, each of which has to be validated against CBA.
Loading...
I was just informed by our unified support contact that the DCR request for supporting set-userphoto with CBA has been denied.
Not sure how to really react to the message from MS on getting rid of legacy auth, while not supporting CBA for automation scenarios.
I didn’t get any details directly from the product team, but just a message relayed through support as below
—–
unfortunately, we were informed that the engineering team will no longer make the investment in this feature. The decision was took after an internal analysis on the feature was performed.
—–
Loading...
seems they just updated the original issue on docs.microsoft.com yesterday with a message that its now implemented – so seems there has been some breakdown in communications internally on the MS team.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
I found I needed to enable the Exchange Service Administrator Role.
$ExoAppSp = (Get-AzureADServicePrincipal -Filter “DisplayName eq ””).ObjectId
$ExoRoleId = (Get-AzureADDirectoryRole | ? {$_.DisplayName -eq “Exchange Service Administrator”}).ObjectId
If ($ExoRoleId –eq “”) {
$RoleTemplate = Get-AzureADDirectoryRoleTemplate | Where-Object {$_.DisplayName -eq “Exchange Service Administrator”}
Enable-AzureADDirectoryRole -RoleTemplateId $RoleTemplate.ObjectId
$ExoRoleId = (Get-AzureADDirectoryRole | ? {$_.DisplayName -eq “Exchange Service Administrator”}).ObjectId
}
Add-AzureADDirectoryRoleMember -ObjectId $ExoRoleId -RefObjectId $ExoAppSp
What are you trying to do with this command?
$ExoAppSp = (Get-AzureADServicePrincipal -Filter “DisplayName eq ””).ObjectId
Good catch! In your examle it would be “DispalyName -eq ‘Exo Background Process'”
Hi Tony,
I trying to grant the Exchange.AsManageApp permission but this option seems to be missing from the Supported legacy APIs.
I did assign this role before, but seems is not be available. Has there been any changes recently with regards to this ?
Please ignore, I found out to my surprise that now you will need to search it within the “APIs my organization uses” by the name : Office 365 Exchange Online. Than, you will be able to assign the necessary permission afterwards.
I’ve been looking for this tidbit for 3 days. You have to go to the “APIs my organization uses” section and you can’t find it by searching for “Exchange” because that returns nothing for some reason. Search for “Office 365” and it’s in “Office 365 Exchange Online”. It’s no wonder this has been delayed so long.
Is there a way to specify ?proxyMethod=RPS with this connection method? In the past I’ve needed this for Set-UserPhoto to work and it seems to error out when I add the -connectionuri with RPS.
I shouldn’t think so (but haven’t tested). This is designed to work with the Exchange Online management module, not the older remote PowerShell connections.
I found this post this morning, and it seems Set-UserPhoto isn’t supported. https://docs.microsoft.com/en-us/answers/questions/98190/set-userphoto-doesn39t-work-with-the-exo-v2-module.html?childToView=223960#answer-223960
As Navin Gupta (one of the program managers for the Exchange Online PowerShell module) notes, there is a DCR (design change request) to include Set-UserPhoto in the scope for CBA. I guess we shall have to wait for the process to work through. This isn’t uncommon because there are hundreds of cmdlets in the EXO set, each of which has to be validated against CBA.
I was just informed by our unified support contact that the DCR request for supporting set-userphoto with CBA has been denied.
Not sure how to really react to the message from MS on getting rid of legacy auth, while not supporting CBA for automation scenarios.
How odd. Just how odd. Is there a name of the person who denied the request?
I didn’t get any details directly from the product team, but just a message relayed through support as below
—–
unfortunately, we were informed that the engineering team will no longer make the investment in this feature. The decision was took after an internal analysis on the feature was performed.
—–
seems they just updated the original issue on docs.microsoft.com yesterday with a message that its now implemented – so seems there has been some breakdown in communications internally on the MS team.
https://docs.microsoft.com/answers/answers/346262/view.html
Done all the stuff, i receive “Access Denied” while trying to connect with ps . Certificate is ok, permissions also … any idea ?
It seems that Vasil’s post is no longer on the Quadrotech blog. Are you aware of an alternate URL to try?
Found what appears to be the blog post on his personal site: https://www.michev.info/Blog/Post/3330/certificate-based-authentication-for-exchange-online-remote-powershell