troubleshooting guides
4 TopicsLessons Learned #550: From a Support Case to Reusable Knowledge
Reaching Lessons Learned #550 is an important milestone for me. However, the value of this series is not only the number of articles published. Each article started with a technical question, an unexpected behavior, a support investigation, or a scenario that required additional testing and analysis. Some cases resulted in a configuration change. Others required a query, a script, a workaround, a product clarification, or a different troubleshooting approach. Over time, I have learned that resolving the immediate issue is only one part of the work. A support case becomes even more valuable when the knowledge gained during the investigation can help another engineer or customer facing a similar situation. Every support case may contain a lesson. The challenge is to identify it, validate it, and make it reusable. Identify the Reusable Lesson Not every detail from a support case needs to become an article. The first step is to identify the part of the investigation that may be useful outside the original scenario. This could be: an unexpected product behavior; a common misunderstanding; a diagnostic query; a troubleshooting method; a configuration requirement; a limitation that may not be immediately visible; a way to interpret a metric or error message; a test that helped confirm the technical explanation. For example, the specific customer environment may be unique, but the method used to distinguish CPU pressure from Data IO pressure may be useful in many other investigations. Similarly, the original application architecture may be complex, but the test used to isolate a network path may be simple and reusable. The objective is not to reproduce the complete support case. The objective is to extract the lesson that may help others. Explain the Symptom Clearly A useful technical article should begin with a behavior that readers can recognize. For example: Connections fail only from one application instance. Query duration increases after a service-tier migration. CPU reaches a high percentage, but the workload remains constrained by another resource. A failover restores normal operation without fully explaining the original cause. A monitoring result appears different from what was initially expected. A reader should be able to determine quickly whether the scenario resembles a problem they are investigating. Describe How the Conclusion Was Reached A solution is more useful when the reader understands how it was validated. For that reason, I normally try to explain: what was initially observed; which evidence was reviewed; which possibilities were considered; which tests were performed; what result supported the conclusion; which limitations remained. The objective is to provide enough context for the reader to understand why the conclusion is reasonable and under which conditions it applies. Separate Mitigation from Explanation A mitigation may restore service without fully explaining the technical cause. For example: restarting an application may reset the connection pool; a failover may disconnect blocking sessions; scaling may increase several resource limits simultaneously; recompiling a query may temporarily produce a better execution plan; reverting a deployment may remove the immediate impact. These actions can be valid and necessary. However, when converting the case into reusable knowledge, it is important to distinguish between: what restored normal operation; what was confirmed as the contributing condition; what remained unconfirmed. This distinction helps prevent a successful recovery action from being interpreted as a complete root-cause explanation. Include Something Practical The most useful articles normally provide something the reader can apply. This may be: a query; a script; a checklist; a sequence of tests; a monitoring recommendation; a comparison table; a list of questions to ask; an example of the expected and unexpected results. Even a short article can be valuable if it gives the reader a practical next step. For example, a troubleshooting article may suggest comparing: affected and unaffected periods; successful and unsuccessful connections; current and previous execution plans; CPU, Data IO, and log write utilization; the original and alternative network paths; behavior before and after one controlled change. The practical element is what transforms an explanation into a reusable resource. Document the Boundaries of the Conclusion A technical conclusion is more reliable when its limitations are clearly described. During a support investigation, the available evidence may not allow us to determine every detail. For example: the historical telemetry may be limited; the behavior may not be reproducible; the exact application request may not be identifiable; the test environment may differ from production; an internal implementation detail may not be externally visible. In these situations, it is useful to explain both what was confirmed and what could not be confirmed. For example: The behavior was reproduced only through the affected network path. The same endpoint and authentication method worked successfully through an alternative path. The tests confirmed that the network path was a relevant condition, although the available evidence did not identify the specific component responsible. This type of conclusion is precise, useful, and transparent. A Simple Model I Normally Follow When deciding whether a support investigation can become reusable knowledge, I normally consider the following sequence: Observe: What behavior was reported or measured? Clarify: What was the exact scope and impact? Investigate: Which evidence was relevant? Reproduce: Could the behavior be tested under controlled conditions? Validate: Which result supported or challenged the explanation? Mitigate: What action reduced the immediate impact? Conclude: What did the available evidence allow us to confirm? Share: Which part of the investigation may help someone else? Not every case follows these steps in the same order, and not every investigation provides a complete answer. However, this approach helps transform an individual technical experience into something that can be understood and reused. Questions That Help Identify a Lessons Learned Article Before writing an article, I normally consider questions such as: Was the behavior unexpected or difficult to interpret? Could the same question affect other Azure SQL users? Was there an important difference between the initial assumption and the final conclusion? Did the investigation produce a useful query, script, or test? Is there a limitation or condition that should be better understood? Can the scenario be explained without customer-specific information? What should another engineer or customer do when facing the same behavior? If the investigation provides a useful answer to one or more of these questions, it may contain a lesson worth sharing. Conclusion After 550 Lessons Learned articles, the most important lesson may be that technical support knowledge should not remain only inside an individual service request. A support case starts with an immediate need: understand the behavior, reduce the impact, and identify the appropriate next action. However, once the investigation is complete, we have an opportunity to go one step further. We can extract the reusable part of the experience, explain how the conclusion was reached, document its limitations, and provide something practical for the next person facing a similar situation. That is how an individual support case can become shared technical knowledge. Resolving a case helps one specific situation. Sharing the validated lesson may help many others avoid starting the same investigation from zero.Modern Authentication (Oauth/OIDC)
The Significance of OAuth 2.0 and OIDC in Contemporary Society. In today's digital landscape, securing user authentication and authorization is paramount. Modern authentication protocols like OAuth 2.0 and OpenID Connect (OIDC) have become the backbone of secure and seamless user experiences. This blog delves into the roles of OAuth 2.0 and OIDC, their request flows, troubleshooting scenarios and their significance in the modern world. Why Oauth 2.0? What problem does it solve? Let's compare Oauth to traditional Forms based Authentication. Aspect OAuth Forms Authentication Password Sharing Eliminates the need for password sharing, reducing credential theft risk. Requires users to share passwords, increasing the risk of credential theft. Access Control Provides granular access control, allowing users to grant specific access to applications. Limited access control, often granting full access once authenticated. Security Measures Enhanced security measures, creating a safer environment for authentication. Susceptible to phishing attacks and credential theft. User Experience Simplifies login processes, enhancing user experience. Can lead to user password fatigue and weak password practices. Credential Storage Does not require storing user credentials, reducing the risk of breaches. Requires secure storage of user credentials, which can be challenging. Session Hijacking Provides mechanisms to prevent session hijacking. Vulnerable to session hijacking, where attackers steal session cookies. OAuth 2.0 Overview OAuth 2.0 is an authorization framework that allows third-party applications to obtain limited access to user resources without exposing user credentials. It provides a secure way for users to grant access to their resources hosted on one site to another site without sharing their credentials. OAuth 2.0 Request Flow Here’s a simplified workflow: Authorization Request: The client application redirects the user to the authorization server, requesting authorization. User Authentication: The user authenticates with the authorization server. Authorization Grant: The authorization server redirects the user back to the client application with an authorization code. Token Request: The client application exchanges the authorization code for an access token by making a request to the token endpoint. Token Response: The authorization server returns the access token to the client application, which can then use it to access protected resources. Let’s take an Example to depict the above Authorization code flow. Consider a front-end .NET core application which is built to make a request to Auth server to secure the token. (i.e. Auth token) the token then will be redeemed to gain access token and passed on to an API to get simple weather details. 1. In program.cs we will have the following code. builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read" }) .AddDownstreamApi("Weather", builder.Configuration.GetSection("Weather")) .AddInMemoryTokenCaches(); The above code configures the application to use Microsoft Identity for authentication, acquire tokens to call downstream APIs, and cache tokens in memory. AddMicrosoftIdentityWebApp This line Registers OIDC auth scheme. It reads the Azure AD settings from the AzureAd section of the configuration file (e.g., appsettings.json). This setup allows the application to authenticate users using Azure Active Directory. EnableTokenAcquisitionToCallDownstreamApi This line enables the application to acquire tokens to call downstream APIs. The user.read scope is specified, which allows the application to read the user's profile information. This is essential for accessing protected resources on behalf of the user. AddDownstreamApi This line configures a downstream API named "Weather". It reads the configuration settings for the Weather API from the Weather section of the configuration file. This setup allows the application to call the Weather API using the acquired tokens. AddInMemoryTokenCaches This line adds an in-memory token cache to the application. Token caching is crucial for improving performance and reducing the number of token requests. By storing tokens in memory, the application can reuse them for subsequent API calls without needing to re-authenticate the user. 2. In applicationsettings.json we will have the following. "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "Domain name", "TenantId": "Add tenant ID", "ClientId": "Add client ID", "CallbackPath": "/signin-oidc", "Scopes": "user.read", "ClientSecret": "", "ClientCertificates": [] }, In the home controller we can inject the IDownstreamApi field into home default constructor. private IDownstreamApi _downstreamApi; private const string ServiceName = "Weather"; public HomeController(ILogger<HomeController> logger, IDownstreamApi downstreamApi) { _logger = logger; _downstreamApi = downstreamApi; } 3. The following section makes an API call. public async Task<IActionResult> Privacy() { try { var value = await _downstreamApi.CallApiForUserAsync(ServiceName, options => { }); if (value == null) { return NotFound(new { error = "API response is null." }); } value.EnsureSuccessStatusCode(); // Throws if response is not successful string jsonContent = await value.Content.ReadAsStringAsync(); return Content(jsonContent, "application/json"); // Sends raw JSON as is } catch (HttpRequestException ex) { return StatusCode(500, new { error = "Error calling API", details = ex.Message }); } } The above code will make sure to capture the token by making call to Identity provider and forward the redeemed access token (i.e. Bearer token) to the backend Api. 4. Now let’s see the setup at the Web Api: In program.cs we will have the following code snippet. var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); Followed by Appsettings.json. "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "Domain name", "TenantId": “Add tenant id", "ClientId": "Add client id.", "CallbackPath": "/signin-oidc", "Scopes": "user.read", "ClientSecret": "", "ClientCertificates": [] }, In the controller we can have the following. namespace APIOauth.Controllers { [Authorize(AuthenticationSchemes = "Bearer")] [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; To drill down the request flow let’s capture a fiddler: Step 1: First 2 calls are made by the application to openid-configuration and Keys end points. The first step is crucial as the application requires Open id configuration to know what configuration it has and what are the supported types. Example: Claims supported; scopes_supported, token_endpoint_auth_methods_supported, response mode supported etc… Secondly the keys endpoint provides all the public keys which can later be used to Decrypt the token received. Step 2: Once we have the above config and keys the application now Redirects the user to identity provider with the following parameters. Points to be noted in the above screen is the response_type which is code (Authorization code) and the response_mode is Form_post. Step 3: The subsequent request is the Post requests which will have the Auth code in it. Step 4: In this step we will redeem the auth code with access token. Request is made by attaching the auth code along with following parameters. Response is received with an access token. Step 5: Now the final call is made to the Api along with the access token to get weather details. Request: Response: This completes the Oauth Authorization code flow. Let us now take a moment to gain a brief understanding of JWT tokens. JWTs are widely used for authentication and authorization in modern web applications due to their compact size and security features. They allow secure transmission of information between parties and can be easily verified and trusted. Structure A JWT consists of three parts separated by dots (.), which are: Header: Contains metadata about the type of token and the cryptographic algorithms used. Payload: Contains the claims. Claims are statements about an entity (typically, the user) and additional data. Signature: Ensures that the token wasn't altered. It is created by taking the encoded header, the encoded payload, a secret, the algorithm specified in the header, and signing that. Here is an example of a JWT: OpenID Connect. (OIDC) OIDC Overview OpenID Connect is an authentication layer built on top of OAuth 2.0. While OAuth 2.0 handles authorization, OIDC adds authentication, allowing applications to verify the identity of users and obtain basic profile information. This combination ensures both secure access and user identity verification. OIDC Request Flow OIDC extends the OAuth 2.0 authorization code flow by adding an ID token, which contains user identity information. Here’s a simplified workflow: Authorization Request: The client application redirects the user to the authorization server, requesting authorization and an ID token. User Authentication: The user authenticates with the authorization server. Authorization Grant: The authorization server redirects the user back to the client application with an authorization code. Token Request: The client application exchanges the authorization code for an access token and an ID token by making a request to the token endpoint. Token Response: The authorization server returns the access token and ID token to the client application. The ID token contains user identity information, which the client application can use to authenticate the user. Example: Consider .Net core application which is setup for user Authentication. Let’s see the workflow. Let’s capture a fiddler once again to see the authentication flow: Step 1: & Step 2: would remain same as we saw in Authorization code flow. Making a call to OpenID configuration & making a call to Keys Endpoint. Step 3: Response type here is “ID token” and not a Auth code as we saw in Authorization code flow. This is an implicit flow since we are not redeeming or exchanging an Auth code. Also, an Implicit flow doesn't need a client secret. Step 4: In a post request to browser, we will receive an ID token. This completes the implicit code flow which will result in getting the ID token to permit the user to the application. Common Troubleshooting Scenarios Implementing OAuth in ASP.NET Core can sometimes present challenges. Here are some common issues and how to address them: 1. Misconfigurations Misconfigurations can lead to authentication failures and security vulnerabilities. For example, loss of internet connection or incorrect settings in the OAuth configuration can disrupt the authentication process. One example which we have faced is servers placed in “DMZ” with no internet access. Server need to make an outbound call to login.microsoft.com or identity provider for getting the metadata for openId/Oauth. 2. Failures due to server farm setup. Loss of saving Data protection keys on different workers. Data protection is used to protect Cookies. For server farm the data protection keys should be persisted and shared. One common issue with data protection keys in OAuth flow is the synchronization of keys across different servers or instances. If the keys are not synchronized correctly, it can result in authentication failures and disrupt the OAuth flow. In memory token caches can also cause re-authentication since the user token might exist in other workers or get purged after a restart. 3. Token Expiration Token expiration can disrupt user sessions and require re-authentication, which can frustrate users. It's essential to implement token refresh functionality to enhance user experience and security. 4. Redirect URI Mismatches Redirect URI mismatches can prevent applications from receiving authorization cods, causing login failures. Ensure that the redirect URI specified in the identity provider’s settings matches the one in your application. 5. Scope Misconfigurations Improperly configured scopes can result in inadequate permissions and restrict access to necessary resources. It's crucial to define the correct scopes to ensure that applications have the necessary permissions to access resources. By understanding these common pitfalls and implementing best practices, developers can successfully integrate OAuth into their ASP.NET Core applications, ensuring a secure and seamless user experience. References: Call a web API from a web app - Microsoft identity platform | Microsoft Learn Microsoft identity platform and OAuth 2.0 authorization code flow - Microsoft identity platform | Microsoft Learn OpenID Connect (OIDC) on the Microsoft identity platform - Microsoft identity platform | Microsoft Learn I hope it helps!2.9KViews2likes2CommentsAutomatic update of Universal Print to v2 - not working as described
Hi everyone, i got the information that we need to update to v2. So i installed Net Framework 4.8 on our "Printer Server" which is a Win2019 Server. In the documentation it says that it will update the next night but nothing it happening. It worked in May for the last update though. At the eventlog (informational) it states: Updates for Connector feature 'all features' are blocked at this hour. So just keep waiting or do i have to take any action? BR Stephan577Views0likes2Comments