Skip to content

Commit 72419d2

Browse files
committed
revert the change
1 parent 235984a commit 72419d2

2 files changed

Lines changed: 310 additions & 232 deletions

File tree

Lines changed: 40 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -1,270 +1,78 @@
11
---
2-
title: Enable Bundled Consent for Multiple Application Registrations in Azure AD
3-
description: Describes how to bundle consent for application registrations
4-
ms.reviewer: willfid
2+
title: Microsoft Entra Applications Using TLS 1.0/1.1 Fail to Authenticate
3+
description: Provides solutions to authentication errors that occur with Microsoft Entra applications using TLS version 1.0 or 1.1.
4+
ms.reviewer: bachoang, v-weizhu
55
ms.service: entra-id
66
ms.date: 05/09/2025
77
ms.custom: sap:Developing or Registering apps with Microsoft identity platform
88
---
9-
# How bundle consent for multiple application registrations
9+
# Microsoft Entra applications using TLS 1.0/1.1 fail to authenticate
1010

11-
In scenarios that you have a custom client application and a custom API. Each registered as separate applications in Microsoft Entra ID. You may want to streamline the user experience by allowing users to consent to both applications at once. This article explains how to configure bundled consent so that users can grant permissions to multiple apps in a single step.
11+
This article provides solutions to authentication errors that occur with Microsoft Entra-integrated applications targeting versions earlier than Microsoft .NET Framework 4.7.
1212

13-
## Step 1: Configure knownClientApplications for the API app registration
13+
## Symptoms
1414

15-
Add the custom client app ID to the custom APIs app registration `knownClientApplications` property. For more information, see [knownClientApplications attribute](/entra/identity-platform/reference-app-manifest#knownclientapplications-attribute).
15+
Applications using an older version of the .NET Framework might encounter authentication failures with one of the following error messages:
1616

17-
## Step 2: Configure API permissions
17+
- > AADSTS1002016: You are using TLS version 1.0, 1.1 and/or 3DES cipher which are deprecated to improve the security posture of Azure AD
1818
19-
Make sure that:
19+
- > IDX20804: Unable to retrieve document from: '[PII is hidden]'
2020
21-
- All required API permissions are correctly configured on both the custom client and custom API app registrations.
22-
- The custom client app registration includes the API permissions defined in the custom API app registration.
21+
- > IDX20803: Unable to obtain configuration from: '[PII is hidden]'
2322
24-
## Step 3: The sign-in request
23+
- > IDX10803: Unable to create to obtain configuration from: 'https://login.microsoftonline.com/{Tenant-ID}/.well-known/openid-configuration'
2524
26-
Your authentication request must use the `.default` scope for Microsoft Graph. For Microsoft accounts, the scope must be for the custom API. This also works for school and work accounts.
25+
- > IDX20807: Unable to retrieve document from: 'System.String'
2726
28-
### Example Request for Microsoft accounts and Work or school accounts
27+
- > System.Net.Http.Headers.HttpResponseHeaders RequestMessage {Method: POST, RequestUri: '\<request-uri>', Version: 1.1, Content: System.Net.Http.FormUrlEncodedContent, Headers: { Content-Type: application/x-www-form-urlencoded Content-Length: 970 }} System.Net.Http.HttpRequestMessage StatusCode UpgradeRequired This service requires use of the TLS-1.2 protocol
2928
30-
```HTTP
31-
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
32-
?response_type=code
33-
&Client_id=72333f42-5078-4212-abb2-e4f9521ec76a
34-
&redirect_uri=https://localhost
35-
&scope=openid profile offline_access app_uri_id1/.default
36-
&prompt=consent
37-
```
38-
> [NOTE!]
39-
> The client will not appear as having permission for the API. This is expected because the client is listed as a knownClientApplication.
40-
41-
### Example request for Work or school accounts only
29+
## Cause
4230

43-
If you are not supporting Microsoft Accounts:
31+
Starting January 31, 2022, Microsoft enforced the use of the TLS 1.2 protocol for client applications connecting to Microsoft Entra services on the Microsoft Identity Platform to ensure compliance with security and industry standards. For more information about this change, see [Enable support for TLS 1.2 in your environment for Microsoft Entra TLS 1.1 and 1.0 deprecation](../ad-dmn-services/enable-support-tls-environment.md) and [Act fast to secure your infrastructure by moving to TLS 1.2!](https://techcommunity.microsoft.com/blog/microsoft-entra-blog/act-fast-to-secure-your-infrastructure-by-moving-to-tls-1-2/2967457)
4432

45-
```http
33+
Applications running on older platforms or using older .NET Framework versions might not have TLS 1.2 enabled. Therefore, they can't retrieve the OpenID Connect metadata document, resulting in failed authentication.
4634

47-
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize
48-
?response_type=code
49-
&client_id=72333f42-5078-4212-abb2-e4f9521ec76a
50-
&redirect_uri=https://localhost
51-
&scope=openid profile offline_access User.Read https://graph.microsoft.com/.default
52-
&prompt=consent
53-
54-
```
55-
56-
### Implementation with MSAL.NET
57-
58-
```http
59-
String[] consentScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
60-
var loginResult = await clientApp.AcquireTokenInteractive(consentScope)
61-
.WithAccount(account)
62-
.WithPrompt(Prompt.Consent)
63-
.ExecuteAsync();
64-
```
35+
## Solution 1: Upgrade the .NET Framework
6536

66-
Consent propagation for new service principals and permissions may take time. Your application should handle this delay.
37+
Upgrade the application to use .NET Framework 4.7 or later, where TLS 1.2 is enabled by default.
6738

68-
### Acquire Tokens for Multiple Resources
39+
## Solution 2: Enable TLS 1.2 programmatically
6940

70-
If your client app needs to acquire tokens for another resource such as Microsoft Graph, you must implement logic to handle potential delays after user consent. Here are some recommendations:
71-
72-
- Use the `.default` scope when requesting tokens.
73-
- Track acquired scopes until the required one is returned
74-
- Add a delay if the result still does not have the required scope.
75-
76-
Currently, if `acquireTokenSilent` fails, MSAL will force you to perform a successful interaction before it will allow you to use `AcquireTokenSilent` again, even if you have a valid refresh token to use.
77-
78-
Here is some sample code of retry logic
41+
If upgrading the .NET Framework isn't feasible, you can enable TLS 1.2 by adding the following code to the **Global.asax.cs** file in your application:
7942

8043
```csharp
81-
public static async Task<AuthenticationResult> GetTokenAfterConsentAsync(string[] resourceScopes)
82-
{
83-
AuthenticationResult result = null;
84-
int retryCount = 0;
85-
86-
int index = resourceScopes[0].LastIndexOf("/");
87-
88-
string resource = String.Empty;
89-
90-
// Determine resource of scope
91-
if (index < 0)
92-
{
93-
resource = "https://graph.microsoft.com";
94-
}
95-
else
96-
{
97-
resource = resourceScopes[0].Substring(0, index);
98-
}
44+
using System.Net;
9945

100-
string[] defaultScope = { $"{resource}/.default" };
101-
102-
string[] acquiredScopes = { "" };
103-
string[] scopes = defaultScope;
104-
105-
while (!acquiredScopes.Contains(resourceScopes[0]) && retryCount <= 15)
106-
{
107-
try
108-
{
109-
result = await clientApp.AcquireTokenSilent(scopes, CurrentAccount).WithForceRefresh(true).ExecuteAsync();
110-
acquiredScopes = result.Scopes.ToArray();
111-
if (acquiredScopes.Contains(resourceScopes[0])) continue;
112-
}
113-
catch (Exception e)
114-
{ }
115-
116-
// Switch scopes to pass to MSAL on next loop. This tricks MSAL to force AcquireTokenSilent after failure. This also resolves intermittent cachine issue in ESTS
117-
scopes = scopes == resourceScopes ? defaultScope : resourceScopes;
118-
retryCount++;
119-
120-
// Obvisouly something went wrong
121-
if(retryCount==15)
122-
{
123-
throw new Exception();
124-
}
125-
126-
// MSA tokens do not return scope in expected format when .default is used
127-
int i = 0;
128-
foreach(var acquiredScope in acquiredScopes)
129-
{
130-
if(acquiredScope.IndexOf('/')==0) acquiredScopes[i].Replace("/", $"{resource}/");
131-
i++;
132-
}
133-
134-
Thread.Sleep(2000);
135-
}
136-
137-
return result;
138-
}
46+
protected void Application_Start()
47+
{
48+
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3; // only allow TLS 1.2 and SSL 3
49+
// The rest of your startup code goes here
50+
}
13951
```
14052

141-
### On the custom API using the On-behalf-of flow
142-
143-
In the same way the client app does, when your custom API tries to acquire tokens for another resource using the On-Behalf-Of (OBO) flow, it may fail immediately after consent. To resolve this issue, you can implement retry logic and scope tracking as the following sample:
144-
145-
```csharp
146-
while (result == null && retryCount >= 6)
147-
{
148-
UserAssertion assertion = new UserAssertion(accessToken);
149-
try
150-
{
151-
result = await apiMsalClient.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
152-
153-
}
154-
catch { }
155-
156-
retryCount++;
53+
## Solution 3: Change web.config to enable TLS 1.2
15754

158-
if (result == null)
159-
{
160-
Thread.Sleep(1000 * retryCount * 2);
161-
}
162-
}
55+
If .NET Framework 4.7.2 is available, you can enable TLS 1.2 by adding the following configuration to the **web.config** file:
16356

164-
If (result==null) return new HttpStatusCodeResult(HttpStatusCode.Forbidden, "Need Consent");
57+
```json
58+
<system.web>
59+
    <httpRuntime targetFramework="4.7.2" />
60+
</system.web>
16561
```
16662

167-
If all retries fail, return an error and throw an error and instruct the client to initial a full consent process.
63+
> [!NOTE]
64+
> If using .NET Framework 4.7.2 causes breaking changes to your app, this solution might not work.
16865
169-
**Example of client code that assumes your API throws a 403**
66+
## Solution 4: Enable TLS 1.2 before running PowerShell commands
17067

171-
```
172-
HttpResponseMessage apiResult = null;
173-
apiResult = await MockApiCall(result.AccessToken);
68+
If you encounter the AADSTS1002016 error while running the PowerShell command `Connect-MSolService`, `Connect-AzureAD`, or `Connect-MSGraph` (from the Microsoft Intune PowerShell SDK module), set the security protocol to TLS 1.2 before executing the commands:
17469

175-
if(apiResult.StatusCode==HttpStatusCode.Forbidden)
176-
{
177-
var authResult = await clientApp.AcquireTokenInteractive(apiDefaultScope)
178-
.WithAccount(account)
179-
.WithPrompt(Prompt.Consent)
180-
.ExecuteAsync();
181-
CurrentAccount = authResult.Account;
182-
183-
// Retry API call
184-
apiResult = await MockApiCall(result.AccessToken);
185-
}
70+
```powershell
71+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
18672
```
18773

188-
## Recommendations and expected behavior
189-
190-
Building an app for handling bundled consent is not as straight forward. Preferably you have a separate process you can walk your users through to perform this bundled consent, provision your app and API within their tenant or on their Microsoft Account and only get the consent experience once. (Separate from actually signing into the app.) If you don’t have this process and trying to build it into your app and your sign in experience, it gets messy and your users will have multiple consent prompts. I would recommend that you build a experience within your app that warns users they may get prompted to consent (multiple times).
191-
192-
For Microsoft Accounts, I would expect minimum of two consent prompts. One for the application, and one for the API.
193-
194-
For work and school accounts, I would expect only one consent prompt. Azure AD handles bundled consent much better than Microsoft Accounts.
195-
196-
Here is a end to end example sample of code. This has a pretty good user experience considering trying to support all account types and only prompting consent if required. Its not perfect as perfect is virtually non-existent.
197-
74+
## References
19875

199-
200-
```csharp
201-
string[] msGraphScopes = { "User.Read", "Mail.Send", "Calendar.Read" }
202-
String[] apiScopes = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/access_as_user" };
203-
String[] msGraphDefaultScope = { "https://graph.microsoft.com/.default" };
204-
String[] apiDefaultScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
205-
206-
var accounts = await clientApp.GetAccountsAsync();
207-
IAccount account = accounts.FirstOrDefault();
208-
209-
AuthenticationResult msGraphTokenResult = null;
210-
AuthenticationResult apiTokenResult = null;
211-
212-
try
213-
{
214-
msGraphTokenResult = await clientApp.AcquireTokenSilent(msGraphScopes, account).ExecuteAsync();
215-
apiTokenResult = await clientApp.AcquireTokenSilent(apiScopes, account).ExecuteAsync();
216-
}
217-
catch (Exception e1)
218-
{
219-
220-
string catch1Message = e1.Message;
221-
string catch2Message = String.Empty;
222-
223-
try
224-
{
225-
// First possible consent experience
226-
var result = await clientApp.AcquireTokenInteractive(apiScopes)
227-
.WithExtraScopesToConsent(msGraphScopes)
228-
.WithAccount(account)
229-
.ExecuteAsync();
230-
CurrentAccount = result.Account;
231-
msGraphTokenResult = await clientApp.AcquireTokenSilent(msGraphScopes, CurrentAccount).ExecuteAsync();
232-
apiTokenResult = await clientApp.AcquireTokenSilent(apiScopes, CurrentAccount).ExecuteAsync();
233-
}
234-
catch(Exception e2)
235-
{
236-
catch2Message = e2.Message;
237-
};
238-
239-
if(catch1Message.Contains("AADSTS650052") || catch2Message.Contains("AADSTS650052") || catch1Message.Contains("AADSTS70000") || catch2Message.Contains("AADSTS70000"))
240-
{
241-
// Second possible consent experience
242-
var result = await clientApp.AcquireTokenInteractive(apiDefaultScope)
243-
.WithAccount(account)
244-
.WithPrompt(Prompt.Consent)
245-
.ExecuteAsync();
246-
CurrentAccount = result.Account;
247-
msGraphTokenResult = await GetTokenAfterConsentAsync(msGraphScopes);
248-
apiTokenResult = await GetTokenAfterConsentAsync(apiScopes);
249-
}
250-
}
251-
252-
// Call API
253-
254-
apiResult = await MockApiCall(apiTokenResult.AccessToken);
255-
var contentMessage = await apiResult.Content.ReadAsStringAsync();
256-
257-
if(apiResult.StatusCode==HttpStatusCode.Forbidden)
258-
{
259-
var result = await clientApp.AcquireTokenInteractive(apiDefaultScope)
260-
.WithAccount(account)
261-
.WithPrompt(Prompt.Consent)
262-
.ExecuteAsync();
263-
CurrentAccount = result.Account;
264-
265-
// Retry API call
266-
apiResult = await MockApiCall(result.AccessToken);
267-
}
268-
```
76+
[Transport Layer Security (TLS) best practices with .NET Framework](/dotnet/framework/network-programming/tls)
26977

27078
[!INCLUDE [Azure Help Support](../../../includes/azure-help-support.md)]

0 commit comments

Comments
 (0)