Error handling asp.net core API to Auth0 M2m Management API

I am creating a security controller within my asp.net core API.  My Blazor Web app connects to the ASP.NET Core API and it communicates to the Auth0 Management API.  I have setup  Auth0 / asp.net core API to utilize M2M.

Everything works as expected (i.e. tokens, rights, etc) BUT for some reason I am not able to catch the status code from Auth0 (i.e. Auth0 is not throwing the error as I would have expected.  Is there anotherWhen I pass a valid auth0id, I get the json response (perfect).  When I pass an invalid ID, I know that Autho throws a 404 error (The user does not exist).  I am wanting to capture Auth0 exception so that I can handle it using Status Codes instead of description.   

The exception handler I have been able to find is ErrorApiException.  If this is not the correct one, what should I be using?  Auth0 is throwing an expection because  I can able to handle viw catch (Exception ex) when (ex.Message == "The user does not exist.").
 
using Auth0.Core.Exceptions;
using Auth0.ManagementApi; (version I am using is 10.0.0)

...

 public class SecurityController (IManagementApiClient managementApiClient, ILogger<ItemsController> logger): ControllerBase
{
     private readonly IManagementApiClient _managementApiClient =                managementApiClient;
....

[HttpGet("User/{auth0id}")]
[Authorize(Policy = "Read:Security_Tables")]

public async Task<ActionResult<UserModel>> GetUser(string auth0id)
        {

            _logger.LogInformation("{UserId} - Get: api/Security/User/{auth0id}", User.FindFirst(ClaimTypes.NameIdentifier)!.Value, auth0id);

            try
            {

                GetUserResponseContent result = await _managementApiClient.Users.GetAsync(
                                           id: auth0id, request: new GetUserRequestParameters
                                           {
                                               IncludeFields = true
                                           });


                UserModel user = new UserModel
                {

                    User_Id = result.UserId,
                    Email = result.Email!,
                    Email_Verified = (result.EmailVerified == true ? true : false),
                    Username = result.Username,
                    Phone_Number = result.PhoneNumber,
                    Name = result.Name!,
                    Nickname = result.Nickname,
                    Given_Name = result.GivenName!,
                    Family_Name = result.FamilyName!,
                    Blocked = (result.Blocked == true ? true : false)

                };


                return Ok(user);
            }

            catch (ErrorApiException ex)
            {
                System.Net.HttpStatusCode statusCode = ex.StatusCode;
                return NotFound(ex.Message);
            }
            catch (Exception ex) when (ex.Message == "The user does not exist.")
            {
                return NotFound(ex.Message);
            }
            catch (Exception ex)
            {
                return BadRequest();
            }



        }

Thanks everyone

Hi @stephane.godbout

Welcome to the Auth0 Community.

You are building an ASP.NET Core security controller that calls the Auth0 Management API using the IManagementApiClient (version 10.0.0) with Machine-to-Machine (M2M) authentication. When you pass a valid Auth0 user ID, the API returns the user data correctly. When you pass an invalid ID, Auth0 returns a 404 error, but you are unable to catch it as an ErrorApiException with the status code. Instead, you are catching it as a generic Exception with the message "The user does not exist."

Your code structure is correct — ErrorApiException is the right exception type to catch. However, your catch block ordering may be preventing the ErrorApiException from being caught properly, or the exception is being thrown but the StatusCode property is not being populated as expected.

[Root Cause]

The issue is likely one of the following:

  1. Catch block ordering is incorrect: In C#, catch blocks are evaluated in order from top to bottom. If you have a catch (Exception ex) block before the catch (ErrorApiException ex) block, the generic Exception block will catch the ErrorApiException first (since ErrorApiException inherits from Exception), and the more specific block will never execute.
  2. The StatusCode property is null or not set: In some versions of the Auth0.ManagementApi SDK, the StatusCode property may not be populated for all error responses. You should verify that ex.StatusCode is not null before using it.
  3. The exception is being thrown from a different source: If the GetAsync() method is not throwing ErrorApiException directly, it may be wrapped in another exception type.

[Solution]

Step 1: Verify catch block ordering

Ensure that your catch (ErrorApiException ex) block comes before any catch (Exception ex) blocks. In C#, catch blocks are evaluated in order, and the first matching block is executed. Since ErrorApiException inherits from Exception, a generic Exception catch block will intercept it if placed first.

Correct order:

try
{
    GetUserResponseContent result = await _managementApiClient.Users.GetAsync(
        id: auth0id, 
        request: new GetUserRequestParameters { IncludeFields = true }
    );
    // ... rest of code
}
catch (ErrorApiException ex)  // MOST SPECIFIC - catch this first
{
    System.Net.HttpStatusCode statusCode = ex.StatusCode;
    _logger.LogError("Auth0 API error: {StatusCode} - {Message}", statusCode, ex.Message);
    return NotFound(ex.Message);
}
catch (Exception ex)  // LEAST SPECIFIC - catch this last
{
    _logger.LogError("Unexpected error: {Message}", ex.Message);
    return BadRequest();
}

Step 2: Verify the StatusCode property is populated

Add logging to confirm that ex.StatusCode is not null and contains the expected HTTP status code:

catch (ErrorApiException ex)
{
    _logger.LogError("ErrorApiException caught");
    _logger.LogError("StatusCode: {StatusCode}", ex.StatusCode);
    _logger.LogError("Message: {Message}", ex.Message);
    _logger.LogError("ApiError: {ApiError}", ex.ApiError);
    
    // Check if StatusCode is null
    if (ex.StatusCode == null)
    {
        _logger.LogWarning("StatusCode is null; using Message instead");
        return NotFound(ex.Message);
    }

    // Use StatusCode for more granular error handling
    if (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        return NotFound("User not found");
    }
    else if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        return Unauthorized("Invalid credentials");
    }
    else if (ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
    {
        return BadRequest(ex.Message);
    }

    return StatusCode((int)ex.StatusCode, ex.Message);
}

Step 3: Inspect the ApiError property for additional details

The ErrorApiException also has an ApiError property that contains structured error information from Auth0:

catch (ErrorApiException ex)
{
    _logger.LogError("StatusCode: {StatusCode}", ex.StatusCode);
    _logger.LogError("Message: {Message}", ex.Message);
    
    if (ex.ApiError != null)
    {
        _logger.LogError("ApiError.Error: {Error}", ex.ApiError.Error);
        _logger.LogError("ApiError.ErrorDescription: {ErrorDescription}", ex.ApiError.ErrorDescription);
    }

    if (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        return NotFound(ex.ApiError?.ErrorDescription ?? ex.Message);
    }

    return StatusCode((int)(ex.StatusCode ?? System.Net.HttpStatusCode.InternalServerError), ex.Message);
}

Step 4: Remove the redundant generic Exception catch block

Since you are now catching ErrorApiException with proper status code handling, you can simplify your exception handling:

[HttpGet("User/{auth0id}")]
[Authorize(Policy = "Read:Security_Tables")]
public async Task<ActionResult<UserModel>> GetUser(string auth0id)
{
    _logger.LogInformation("{UserId} - Get: api/Security/User/{auth0id}", 
        User.FindFirst(ClaimTypes.NameIdentifier)!.Value, auth0id);

    try
    {
        GetUserResponseContent result = await _managementApiClient.Users.GetAsync(
            id: auth0id, 
            request: new GetUserRequestParameters { IncludeFields = true }
        );

        UserModel user = new UserModel
        {
            User_Id = result.UserId,
            Email = result.Email!,
            Email_Verified = result.EmailVerified ?? false,
            Username = result.Username,
            Phone_Number = result.PhoneNumber,
            Name = result.Name!,
            Nickname = result.Nickname,
            Given_Name = result.GivenName!,
            Family_Name = result.FamilyName!,
            Blocked = result.Blocked ?? false
        };

        return Ok(user);
    }
    catch (ErrorApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        _logger.LogWarning("User not found: {Auth0Id}", auth0id);
        return NotFound($"User {auth0id} not found");
    }
    catch (ErrorApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        _logger.LogError("Unauthorized: Invalid M2M credentials");
        return Unauthorized("Invalid credentials");
    }
    catch (ErrorApiException ex)
    {
        _logger.LogError("Auth0 API error: {StatusCode} - {Message}", ex.StatusCode, ex.Message);
        return StatusCode((int)(ex.StatusCode ?? System.Net.HttpStatusCode.InternalServerError), ex.Message);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error retrieving user");
        return BadRequest("An unexpected error occurred");
    }
}

Step 5: Verify M2M token and permissions

If ErrorApiException is still not being thrown, verify that your M2M authentication is working correctly:

try
{
    // Test the connection by making a simple API call
    var testUser = await _managementApiClient.Users.GetAsync("test-invalid-id");
}
catch (ErrorApiException ex)
{
    _logger.LogError("M2M test - StatusCode: {StatusCode}, Message: {Message}", 
        ex.StatusCode, ex.Message);
}

If this test call also fails to throw ErrorApiException, there may be a version-specific issue with Auth0.ManagementApi v10.0.0.

Why StatusCode May Be Null:

The StatusCode property is of type System.Net.HttpStatusCode? (nullable). It may be null if:

  1. The exception was thrown before the HTTP response was received
  2. The SDK version has a bug where StatusCode is not being populated
  3. The exception is being thrown from a different layer (e.g., network error, serialization error)

Always check for null before using ex.StatusCode.

Version Consideration:

You are using Auth0.ManagementApi v10.0.0. If the issue persists after following these steps, consider:

  1. Checking the release notes for v10.0.0 to see if there are known issues with exception handling
  2. Testing with a different version (e.g., v9.x) to see if the issue is version-specific
  3. Contacting Auth0 Support with your exact code and the output of the logging statements above

We hope this resolves the issue. Please follow up with the diagnostic information from Step 2 if the problem persists.

Kind regards,
Nik