ADFS Claims Rules to block Outlook External Access (including MAPI/HTTP) to Office 365

K B
K B used Ask the Experts™
on
https://gallery.technet.microsoft.com/office/Client-Access-Policy-30be8ae2/

I would like to use this tool to achieve what I have mentioned in the title, however I have seen comments that some companies have users that are still able to connect with MAPI/HTTP.

So I found this article which mentions how to properly correct this issue but apparently backend changes have even broken this solution:
http://c7solutions.com/2014/07/continuing-adventures-in-ad-fs-claims-rules 

Has anyone successfully prevented Outlook External Access to Office 365 with anything other than conditional access and modern authentication and how did you accomplish it?

Thank you.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
MAPI/HTTP makes no difference, however using Modern authentication does. In general, use the guidance here: https://technet.microsoft.com/en-us/library/dn592182.aspx

If using modern auth, do not use the x-ms-client-application claim in your claims rules, it's simply not added anymore. Another important thing to remember is that with Modern auth, all requests are hitting the /adfs/ls endpoint, Exchange ones included.
K B

Author

Commented:
Thank you Vasil for your reply.  

So I can choose to block Outlook (MAPI and OA) only and allow all other clients externally with Claims rules in ADFS 3.0?  It is odd that many comments are made about their Outlook clients are sometimes able to connect with MAPI/HTTP.

Does Modern Authentication require that I use INTUNE?  Where do I enable modern authentication?  Is it possible with Exchange 2010/2016/365 hybrid?

note: This customer has about 50,000 mailboxes all Exchange 2010 with plans to introduce Exchange 2016 Hybrid servers and ADFS.

Thanks again.
Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
Well, nobody says I'm always right, maybe you can give a link or two to see the details? :)

Modern authentication is enabled per-service in O365: https://social.technet.microsoft.com/wiki/contents/articles/36101.office-365-enable-modern-authentication.aspx
You also need to have the clients enabled: https://support.office.com/en-gb/article/Using-Office-365-modern-authentication-with-Office-clients-776c0036-66fd-41cb-8928-5495c0f9168a
It does not require Intune or any other license.
Ensure you’re charging the right price for your IT

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden using our free interactive tool and use it to determine the right price for your IT services. Start calculating Now!

K B

Author

Commented:
Vasil,

I have never seen you be wrong but I suppose it happens ;-)

In the comments section of...

http://c7solutions.com/2014/07/continuing-adventures-in-ad-fs-claims-rules

and

https://technet.microsoft.com/en-us/library/hh526961(v=ws.10).aspx

...where users speak of the issue blocking access to Outlook that were previously blocked by AD FS claims rules.

This customer does not have Outlook Anywhere enabled on their large Exchange 2010 environment, therefore the Outlook fat-client is restricted to on-premises.  Once we migrate some mailboxes to the cloud, they would like to continue to restrict Outlook access to their on-premises IP addresses AND a select few external IP addresses.  

Will MAPI/HTTP throw a monkeywrench into using AD FS claims rules?

How could modern authentication help?

Thank you Vasil!
Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
Well, if you have a rule that explicitly lists Microsoft.Exchange.RPC for the value of the x-ms-client-application claim, you would need to update it to include Microsoft.Exchange.Mapi. I dont use the policy builder tools as I prefer the examples from the TechNet article, where instead of blocking each specific protocol the rule is only allowing ActiveSync. So I dont think you would have problems blocking external access, most you'll have to do is update the claims rule in question.
K B

Author

Commented:
EDIT:  I am confirming this still

So I have tested this a bunch.  Maybe you can shed some light on better way to test with a claim rule?

With the technet article, I did try to just allow ActiveSync to Office 365, then tested Outlook 2016, 2013 and 2010 and all were able to connect with MAPI/HTTP.

Among several other tests, I most recently tried to block all external access to Office 365 with this rule.

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy"])
 && NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip", Value =~ "\b88\.211\.172\.30\b"])
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

Open in new window


I was still able to connect with Outlook until I added this registry key:

Key: HKEY_CURRENT_USER\Software\Microsoft\Exchange
DWORD: MapiHttpDisabled
Value: 1

Once I restarted Outlook, I was unable to connect (credential prompt that keeps prompting even after you enter correct credentials)

Any ideas?

Thank you again for your time Vasil.
Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
Which version of AD FS is that? If using AD FS 3.0 you need to remove the x-ms-proxy, this is only for AD FS 2.0 and the use of the built-in AD FS proxy. Use the
insidecorporatenetwork claim instead, or the x-ms-forwarded-client-ip.


Here's an example rule I just tested in my lab:

NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip", Value =~ "\bXXX\.XXX\.XXX\.XXX\b"])  && NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value == "/adfs/ls/"]) && NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value == "Microsoft.Exchange.ActiveSync"]) && NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent", Value =~ "lync|ucmapi|WLMHttpTransport|Lync"]) => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

Open in new window


Notes:

 - It will NOT block modern auth due to the /adfs/ls override. In other words if modern auth is enabled in the service (which is by default) and enabled on the client (default for Outlook 2016), the rule will not block external access.
 - It WILL block access for legacy auth for requests coming outside of the designated IP/ranges.
 - Depending on the AD FS setup, the x-ms-forwarded-client-ip might not be present (it's added by the WAPs when ADAL is enabled, if using legacy auth it should always be present).
 - When you disable MAPI/HTTP, you're effectively disabling Modern authentication for the client. With the above in mind, it might explain why your rule is not working.
 - The other part is an exception for SfB, you can safely remove it.

In case I've missed something (still early morning here), here's a similar discussion we had recently on the Microsoft Tech Community, you can see more examples there: https://techcommunity.microsoft.com/t5/Identity-Authentication/ADFS-Claims-Based-Rules-I-m-stuck/td-p/24986
K B

Author

Commented:
Thank you Vasil,

I am just sitting down to my lab now.

I have

1 x ADFS Web Proxy 3.0 server
1 x ADFS 3.0 server

I would like to use the defaults (modern authentication) on the service and client.

What would change in the command?

Thank you.
K B

Author

Commented:
I am testing now with the the default rule in the technet article for 2012 R2 ADFS
https://technet.microsoft.com/en-us/library/dn592182.aspx

Block all external access to Office 365

c1:[Type == " http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] && c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip", Value =~ "^(?!192\.168\.1\.77|10\.83\.118\.23)"] => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = " DenyUsersWithClaim");

Open in new window


...without changing anything (except removing the space next first quote mark) I created one Claim Rule and made it first on the list.   I just left the IP addresses that came with the rule as I am testing from my office only and the IPs are not my office(right?).  I removed the registry entry for mapihttpdisabled. OWA seems to be blocked for me.   Outlook however, continues to work with both MAPI/HTTP and RPC/HTTPS.

Any ideas?
K B

Author

Commented:
I am re-reading your comment,

Lastly, I'd recommend avoiding the use of both insidecorporatenetwork and x-ms-forwarded-client-ip in the same rule.

I will try to play with this.. why does M$ recommend this?
K B

Author

Commented:
Okay I am realizing I need to test Outlook OFF my lab's network as ActiveSync works ON my network but not OFF.  I think my presumption of the IP addresses not being "mine" was wrong.  I will set the rule to have my actual IPs and drive to a public wifi and test properly now.
K B

Author

Commented:
I am about to go to the public wifi.  The Remote Connectivity Analyzer seems to succeed however I will test Outlook off my network shortly.

Side note: I noticed when I tried the Client Access Policy Builder script that is adds "Acceptance Transform Rules".  I removed everything that the script added and am only testing with:

c1:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

Open in new window


but I was curious why it adds "Acceptance Transform Rules"?  Do I need them?

This is the image from the scripts webpage (as I deleted these already).  Do I need to put them back?

acceptancerules.png
I backed up the text... so here is what I deleted:

Pass through: x-ms-forwarded-client-ip

c:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip"]
 => issue(claim = c);

 
Pass through: x-ms-client-application

c:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application"]
 => issue(claim = c);

 
Pass through: x-ms-client-user-agent

c:[Type == "p://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent"]
 => issue(claim = c);

 
Pass through: x-ms-proxy

c:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy"]
 => issue(claim = c);

 
Pass through: x-ms-endpoint-absolute-path

c:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path"]
 => issue(claim = c);

Open in new window

Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
The last rules shouldnt be needed, the tool is written a long time ago and accounts for situations where you have a very old version of AD FS.

It's not that Microsoft does not recommends using the x-ms-forwarded-client-ip and the insidecorporatenetwork together, but they're both serving a similar purpose. The more claims you have in the rule, the bigger the chance for error, thus I prefer starting with the simplest possible rule when troubleshooting.

How are the tests going? Judging by the rule, you're trying to block any external access, not just Outlook, right? For external tests you can use some VM in Azure if you have credit there, takes few mins tops (ExRCA is down here too).  

Oh and just one side note, since you mentioned Conditional access in the original post - they recently introduced conditional access based on location for SPO/ODFB *without* any licensing requirements: https://techcommunity.microsoft.com/t5/SharePoint-Blog/Introducing-Conditional-Access-by-Network-Location-for/ba-p/39274
K B

Author

Commented:
Seems to be working now that I am offsite - for all external access (tried the most simple first).  Now I am trying to add in && "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application" to only restrict Outlook from being used externally.

Strangely, the newer article mentions just these 4 - One is a dupe:

Use of the Microsoft Outlook client may result in any of the following values:

Microsoft.Exchange.Autodiscover
Microsoft.Exchange.OfflineAddressBook
Microsoft.Exchange.RPCMicrosoft.Exchange.WebServices
Microsoft.Exchange.RPCMicrosoft.Exchange.WebServices

Open in new window


and the older article - RPC in a different syntax mentions 5 (including MAPI):

Use of the Microsoft Outlook client may result in any of the following values:

Microsoft.Exchange.Autodiscover
Microsoft.Exchange.OfflineAddressBook
Microsoft.Exchange.RPC
Microsoft.Exchange.WebServices
Microsoft.Exchange.Mapi

Open in new window

What I have now is:

c1:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

I am unsure of a few things:

  1. Should I be using "c1:" etc in the syntax?
  2. How do I combine all the x-ms-client-application(s) into one statement (I suppose I just want the rule I have now AND "if Outlook" - then block)
  3. How do I account for Modern Authentication (passive request I am presuming, right? http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path)
  4. What should the syntax look like over all.. I can't seem to find examples where x-ms-client-application is being used more than once or combined as one rule

Thank you!
K B

Author

Commented:
this is what I am testing now:


exists([Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"])
 && exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value == "Microsoft.Exchange.Autodiscover|Microsoft.Exchange.OfflineAddressBook|Microsoft.Exchange.RPC|Microsoft.Exchange.WebServices|Microsoft.Exchange.Mapi"])
 && exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value == "/adfs/ls/"])
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

Open in new window

K B

Author

Commented:
That did not seem to work so I broke it out into two rules, still no luck...

First Rule:
exists([Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"])
 && exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value == "Microsoft.Exchange.Autodiscover|Microsoft.Exchange.OfflineAddressBook|Microsoft.Exchange.RPC|Microsoft.Exchange.WebServices|Microsoft.Exchange.Mapi"])
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

Open in new window


Second Rule:
exists([Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"])
 && exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path", Value == "/adfs/ls/"])
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

Open in new window

Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
Here you will run into issues with Modern authentication - any requests related to it will *not* include the x-ms-client-application claim. This claim is inserted by the EO servers when the active endpoint is used, in other words it's only added when using legacy auth. If you want to distinguish between clients, use the  x-ms-client-user-agent claim, and check for "outlook" value.

I've written about this in more detail here: http://blog.enowsoftware.com/solutions-engine/ad-fs-claims-rules-and-modern-authentication
K B

Author

Commented:
Okay I will check out the article now... can't thank you enough for all your help!

I just noticed that my tenant does not have modern authentication enabled!  could that be an issue?
K B

Author

Commented:
Get-OrganizationConfig | ft name, *OAuth*

Name                      OAuth2ClientProfileEnabled
----                      --------------------------
contoso.onmicrosoft.com                      False
K B

Author

Commented:
At this point I cannot block Outlook regardless of the rule I use.. Even "block all external" rule:

c1:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

Open in new window


Is there a way to accomplish this another way?  I know there is a GUI with Windows Server 2016 AD FS but does that change anything?
Does AD FS 2.0 or 2.1 work for this scenario, while later versions do not?  I have spent 3 days on this and I am really glad I have as I have learned a ton!  But, I wonder how I can accomplish my original goal, "Block External Access to Outlook Only".

Thoughts?
Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
If Modern auth is off, you should be able to use the x-ms-client-application and in general the examples in the article as they are. The rule I used above is exactly for that situation - block Outlook externally, without affecting browser applications, ActiveSync or SfB.

When testing, make sure to observe in the AD FS event logs whether the client actually made a request to the AD FS. Due to token caching it might be reusing the old token, thus the changes in the rule you made might not be taken into consideration.
K B

Author

Commented:
Vasil,

Thank you for pointing me to the logs!

I am now creating new mailboxes to test to circumvent token caching.

With the same rule below, I am again able to block all external.  When blocked, it logs to the AD FS/ADMIN event log.

c1:[Type == " http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "  
DenyUsersWithClaim");

Open in new window


However, when I attempt to block with X-MS-Client-Application per this rule...

c1:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
 && c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value == "Microsoft.Exchange.Mapi|Microsoft.Exchange.Autodiscover|Microsoft.Exchange.OfflineAddressBook|Microsoft.Exchange.WebServices"]
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

Open in new window


... nothing is ever logged to the ADFS/ADMIN log.  However, I do see events in the Security Log like pictured:

X-MS-Client-Application.png
Outlook is neither being prevented from being configured nor connecting to Exchange Online.

Any ideas as to why or where to look in the logs to see why?
Most Valuable Expert 2015
Distinguished Expert 2018
Commented:
Seems like the request was approved, you dont get those copied to the Admin log. If you are missing the additional details about approved alerts in the Security log, enable auditing as detailed here: https://jorgequestforknowledge.wordpress.com/2013/07/08/enabling-auditing-of-issued-claims-in-adfs-v2-x-and-adfs-v3-x/

Also, update your rule to do pattern matching:

c1:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"]
 && c2:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value =~ "Microsoft.Exchange.Mapi|Microsoft.Exchange.Autodiscover|Microsoft.Exchange.OfflineAddressBook|Microsoft.Exchange.WebServices"]
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

Open in new window


(change the == on the Value comparison to =~ to do a regex match)
K B

Author

Commented:
Oh my!  I think it is working!

But why does the pattern match come into play?  Is it necessary because of the OR: "|"  ?
K B

Author

Commented:
GENIUS!

Thank you Thank you Thank you!!!!
K B

Author

Commented:
Vasil,

Apologies, but this is blocking the same mailboxes from the inside as well.
K B

Author

Commented:
Oh got it!

NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip", Value =~ "\bXXX\.XXX\.XXX\.XXX\b"])
 && exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application", Value =~ "Microsoft.Exchange.Mapi|Microsoft.Exchange.Autodiscover|Microsoft.Exchange.OfflineAddressBook|Microsoft.Exchange.WebServices"])
 => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim");

Open in new window

Most Valuable Expert 2015
Distinguished Expert 2018

Commented:
You are basically using a regex for the x-ms-client-application value comparison, thus the pattern match. Same for the x-ms-forwarded-client-ip. If you are comparing against a single, well-known value such as "false", you can use "regular" match.

Glad it's finally working, you're certainly familiar with pretty much every aspect of creating and troubleshooting AD FS claims rules now :)

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial