O365 Client Access Policies
Without enabling Modern Authentication (ADAL) on O365, ADFS is able to provide flexible Access policies. The configuration of and these policies are seen in these pages:
- Configuring Client Access Policies
- Limiting Access to Office 365 Services Based on the Location of the Client
- Client Access Policy Builder
From the above pages we can see there are 4 main scenarios the policies cover:
- Scenario 1 - Block all external access to Office 365
- Scenraio 2 - Block all external access to Office 365 except Exchange ActiveSync
- Scenario 3 - Block all external access to Office 365 except browser-based applications
- Scenario 4 - Block all external access to Office 365 except for designated Active Directory groups
For each of those scenarios custom claim types are generated and used to create the policy. Here is a note from the above pages:
The policies described in this article make use of two kinds of claims
- Claims AD FS creates based on information the AD FS and Web Application proxy can inspect and verify, such as the IP address of the client connecting directly to AD FS or the WAP.
- Claims AD FS creates based on information forwarded to AD FS by the client as HTTP headers
WS-Federation VS WS-Trust
Before we get into the scenarios it's important to understand WS-Federation (Passive Profile) VS WS-Trust (Active Profile). The Understanding WS-Federation page covers the topic in great detail. To summarize here are some excerpts from the page:
WS-Trust provides an additional piece of the foundation for federation by defining a service model, the Security Token Service (STS), and a protocol for requesting/issuing these security tokens which are used by WS-Security and described by WS-SecurityPolicy.
WS-Trust introduces protocol mechanisms independent of any particular application for requesting, issuing, renewing, cancelling and validating security tokens which can be exchanged to authenticate principals and protect resources. The core of this protocol is the request-response message pair, Request Security Token (RST) and Request Security Token Response (RSTR).
And here are some excerpts about WS-Federation:
A fundamental goal of WS-Federation is to simplify the development of federated services through cross-realm communication and management of Federation Services by re-using the WS-Trust Security Token Service model and protocol.
WS-Federation defines syntax for expressing the WS-Trust protocol and WS-Federation extensions in a web browser only environment using widely supported HTTP 1.1 mechanisms (GET, POST, redirects, and cookies). WS-Federation defines encoding rules that enable many of the WS-Trust protocol extensions to be accessible via HTTP 1.1 mechanisms by standard browser clients and web applications.
And from Windows Identity Foundation 101’s : WS-Federation Passive Requestor Profile (part 2 of 2) here is a pretty good diagram of WS-Federation Passive Profile:
- End-user attempts to access RP website
- Client is redirected to IdP website
- End-user logs in
- IdP sends a claim-rich token back to the client
- Client presents token to RP
I think in summary with WS-Trust it's usually used for rich clients and require an STS (Security Token Service) to function. While with WS-Federation it can be used for Browser Based clients when the Passive Profile is utilized. People sometimes link WS-Trust with Active Endpoints and WS-Federation with Passive endpoints.
Scenario 1 without ADAL with ADFS
Here is the rule for the first scenario:
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"); c: => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
The most important new claim type is x-ms-forwarded-client-ip. The policy is basically checking to see if the value of that claim type is not an internal IP, and if it's not ADFS denies access. From the Configuring Client Access Policies page here is note about that claim type:
This AD FS claim represents a “best attempt” at ascertaining the IP address of the user (for example, the Outlook client) making the request. This claim can contain multiple IP addresses, including the address of every proxy that forwarded the request. This claim is populated from an HTTP header. The value of the claim can be one of the following:
- A single IP address - The IP address of the client that is directly connected to Exchange Online
- One or more IP addresses
- If Exchange Online cannot determine the IP address of the connecting client, it will set the value based on the value of the x-forwarded-for header, a non-standard header that can be included in HTTP based requests and is supported by many clients, load balancers, and proxies on the market.
So Exchange Online creates this claim (either from an HTTP Header - set by a web proxy - or directly from the client it self).
Scenario 1 with ADAL with ADFS
If ADAL is enabled then the active end point is not longer used, everything happens over the passive endpoint. There are actually a couple of good sites that talk about Modern Authentication and the Access Policies:
- AD FS Claims Rules and Modern Authentication
- Office 2013 and Office 365 ProPlus modern authentication and client access filtering policies : Things to know before onboarding
By using the insidecorporatenetwork claim type ADFS is able to check if the client is internal or external. From the above links:
reminder: this claim is added by the WAP server or any other AD FS proxy replacement
Similar to the above x-ms-proxy claim type, this claim type indicates whether the request has passed through the web application proxy. Unlike x-ms-proxy, insidecorporatenetwork is a boolean value with True indicating a request directly to the federation service from inside the corporate network.
So using external tools we can figure out if the request is internal or external. Here is a note about active vs passive:
With modern authentication, all clients will use Passive Flows (WS-Federation), and will appear to be browser traffic to AD FS.
Note that we didn’t include a check for which endpoint the request came from. The reason being that with Modern authentication, every request from ADAL-enabled clients will be hitting the passive endpoint.
Scenario 1 with ADAL with RSA Via Access
With the current policies capabilities we can create a rule to check the IP from which the request came and make a decision on it. For example:
If not internal deny
else allow all
I ended up using the following regular expression to figure out if the IP is not internal
Here is how the policy looked like in the Access Console:
That will be the important rule.
Scenario 2 without ADAL with ADFS
Here is the rule for the second scenario:
The rules get pretty creative with figuring out if the access is internal or external but the most important rule is the one using the x-ms-client-application claim type. Here is note about this claim type:
This AD FS claim represents the protocol used by the end client, which corresponds loosely to the application being used. This claim is populated from an HTTP header that is currently only set by Exchange Online, which populates the header when passing the authentication request to AD FS. Depending on the application, the value of this claim will be one of the following:
- In the case of devices that use Exchange Active Sync, the value is Microsoft.Exchange.ActiveSync.
- Use of the Microsoft Outlook client may result in any of the following values:
- Other possible values for this header include the following:
So Exchange Online generates that header and includes that in the request to ADFS and ADFS passes that header/claim through and uses it for it's policy. Looking over Limit Access to Office 365 Based on the Location of Client the pages covers some of the HTTP headers that are passed to an STS from a client:
Office 365 sends information about application name, client IP, useragent, proxy information to STS as part of HTTP request. Solution to restrict user access to STS can be implemented via ServletFilter. Filter will look for following header names:
Scenario 2 with ADAL with ADFS
Here is note from one of the above sites (talking about the x-ms-client-application claim type) :
As some of you might recall, this claim is only available for Exchange Online related requests and is inserted by the Exchange server during the process of proxying the authentication request to the AD FS on behalf of the client. With modern authentication enabled, this claim will simply not be present in the request, as the client now gets the token directly from the AD FS server and the Exchange server plays no role in the process.
So now we can use the x-ms-client-user-agent claim type instead of the . From Restrict iOS apps which can access to Office 365 services (ADFS required) it has an example of what some user agents look like:
Here’s the user agent of Microsoft Outlook/Word/Excel/PowerPoint/OneDrive/Intune Managed Browser on an iOS 8.4.1 device.
Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H321
And this is an example of user agent of Safari browser on iOS 8.4.1:
Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4
Last example is the user agent of Chrome browser on iOS 8.4.1:
Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12H321 Safari/600.1.4
Microsoft Word for Android user agent:
Mozilla/5.0 (Linux; Android 5.0.1; GT-I9505 Build/LRX22C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36 PKeyAuth/1.0
Intune Managed Browser for Android user agent:
Mozilla/5.0 (Linux; Android 5.0.1; GT-I9505 Build/LRX22C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36
So then using that claim type the following rule was created from that page:
exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent”, Value =~ “^Mozilla\/5.0 \((iPhone|iPad|iPod); CPU iPhone (?:OS\s*\d+_\d+(?:_\d+)?\s*)? like Mac OS X\) (?:AppleWebKit\/\d+(?:\.\d+(?:\.\d+)?|\s*\+)?\s*)? \(KHTML, like Gecko\) (?:Mobile\/\w+\s*)?$”]) => issue(Type = “http://schemas.microsoft.com/authorization/claims/permit”, Value = “true”);
BTW here is the description of that claim type:
This AD FS claim provides a string to represent the device type that the client is using to access the service. This can be used when customers would like to prevent access for certain devices (such as particular types of smart phones). Example values for this claim include (but are not limited to) the values below.
The below are examples of what the x-ms-user-agent value might contain for a client whose x-ms-client-application is “Microsoft.Exchange.ActiveSync”
It is also possible that this value is empty.
Since this is not created by Exchange Online but ADFS it self we can still use it with ADAL clients.
Scenario 2 with ADAL with RSA Via Access
With the current policy set we can also utilize the User Agent HTTP Header, looking over Browser detection using the user agent I ended up creating the following regex to determine if the connection is coming from a mobile device/browser:
And then I used that to allow user agents that are mobile based. Here is how the policy looks like in the Access Console:
Scenario 3 without ADAL with ADFS
Here is how the rule looks like:
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://custom/ipoutsiderange", Value = "true"); c1:[Type == "http://custom/ipoutsiderange", Value == "true"] && c2:[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 = " DenyUsersWithClaim"); c: => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
Similar process is used for internal IP testing and the important claim type is x-ms-endpoint-absolute-path and here the information about that claim type from the same page:
This claim type can be used for determining requests originating from “active” (rich) clients versus “passive” (web-browser-based) clients. This enables external requests from browser-based applications such as the Outlook Web Access, SharePoint Online, or the Office 365 portal to be allowed while requests originating from rich clients such as Microsoft Outlook are blocked.
The value of the claim is the name of the AD FS service that received the request.
We can see from the rule that we are checking is the request is not coming into the passive endpoint (/adfs/ls/) then we know it's going to the active endpoint (which I believe is /adfs/services/trust/2005/usernamemixed.. or any other end point really) and therefore will be denied.
Scenario 3 with ADAL with ADFS
From Office 2013 and Office 365 ProPlus modern authentication and client access filtering policies : Things to know before onboarding it's mentioned that this Scenario is no longer support:
This scenario is not yet supported for public preview and we recommend organizations that rely on this scenario to not onboard their tenants for modern authentication.
If scenario # 3 applies to you, and you enable modern authentication on your tenant, rich clients (Outlook and other Office apps) will be able to bypass your client access filtering policies and in ADFS access resources like Exchange Online and SharePoint online.
This kind of makes sense since all ADAL enabled clients will only use the Passive Endpoint just like browser Based clients.
Scenario 3 with ADAL with RSA Via Access
I was testing out some of the User Agent stuff and here is what I saw:
Office 2016 Outlook
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Microsoft Outlook 16.0.6769)
Android Nexus 5.0
Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MMB29X; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36 PKeyAuth/1.0
Mac OS X Chrome
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/49.0.2623.112 Safari/537.36
Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13D15 Safari/601.1
Mac OS Firefox
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:45.0) Gecko/20100101 Firefox/45.0
Office 2016 Word/PowerPoint/Excel
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3. 5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)
IE 11 Windows
Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
So we can see that Non-Outlook Rich Clients show up as Internet Explorer 7 (MSIE 7.0, here is full list of IE User Agents: Internet Explorer User Agent Strings). So we can basically say that if the User Agent is not Outlook or IE 7 (hopefully no one is using that old browser) then we are using a regular browser (you can get creative and block mobile browsers as well if you'd like). Here is the regex I ended up using:
And the rule looked like this in the Access Console:
Then when I tried to login from a rich client it blocked the access:
Another thing we can do is use Windows Intune to block Active Sync or Mailbox Access at the Exchange Online Level (a post for another time). Here are a couple of sites that about that setup:
Scenario 4 without ADAL with ADFS
This one doesn't use any special headers it just use the groupsid claim, here is the rule:
c1:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip", Value =~ "^(?!192\.168\.1\.77|10\.83\.118\.23)"] && c2:[Type == "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork", Value == "false"] => issue(Type = "http://custom/ipoutsiderange", Value = "true"); NOT EXISTS([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-32-100"]) => add(Type = "http://custom/groupsid", Value = "fail"); c1:[Type == "http://custom/ipoutsiderange", Value == "true"] && c2:[Type == "http://custom/groupsid", Value == "fail"] => issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "DenyUsersWithClaim"); c: => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
The rule just checks if the groupsid claim is there (and this is created by ADFS not any proxy) and if it's not the user is denied.
Scenario 4 with ADAL with ADFS
This won't change and will work without any changes, since the policy mainly depends on the groupsid claim type.
Scenario 4 with ADAL with RSA Via Access
Same goes for RSA Via here, we can create a policy based on group membership and deny or allow access based on that. The configuration is covered in a previous post: Combining Multiple Rules Into A Single Rule to Create Complex Rule Sets