Issues with Access Token for Management API in C#

Hey there,

I’m quite new to using Auth0, so sorry in advance if I misunderstood something in the documentation.

I’m building a small library to manage roles and permissions for my application through a secure backend. I figured the Management API would be the best approach for this. After reading through the documentation, I created a testing Management API access token, and that works perfectly.

For production, I understand that requesting an access token only when needed is recommended. So, I wrote the following class to manage ManagementApiClient instances, ensuring it works when an access token is already configured (in my case, through .NET User Secrets):

My Implementation

using Auth0.ManagementApi;
using InfiniLore.Credentials.Auth0.Contracts;
using Microsoft.Extensions.Options;
using RestSharp;
using System.Text.Json;

namespace InfiniLore.Credentials.Auth0;

public class Auth0ClientManager(IOptions<Auth0Options> options) : IAuth0ClientManager {
    private readonly Auth0Options _settings = options.Value;
    private string AccessToken { get; set; } = options.Value.AccessToken ?? string.Empty ;
    private DateTime _tokenExpiry = DateTime.MinValue;
    private string? _scopes;
    private string? _tokenType;

    private IManagementApiClient? Client { get; set; }

    // -----------------------------------------------------------------------------------------------------------------
    // Methods
    // -----------------------------------------------------------------------------------------------------------------
    public void ResetClient() {
        AccessToken = string.Empty;
        _tokenExpiry = DateTime.MinValue;
        _scopes = null;
        _tokenType = null;

        Client = null;
    }
    
    public async ValueTask<IManagementApiClient> GetClientAsync(CancellationToken ct = default) {
        if (_tokenExpiry != DateTime.MinValue && DateTime.UtcNow - TimeSpan.FromMinutes(5) > _tokenExpiry) 
            ResetClient(); // Ensure a fresh client if token expired
        
        return Client ??= await ClientFactory(ct);
    }
    
    private async Task<IManagementApiClient> ClientFactory(CancellationToken ct = default) {
        if (string.IsNullOrWhiteSpace(_settings.Domain)) throw new InvalidOperationException("Domain is required and cannot be empty.");
        if (!Uri.IsWellFormedUriString($"https://{_settings.Domain}", UriKind.Absolute))
            throw new InvalidOperationException($"Domain '{_settings.Domain}' is not a valid URI.");

        
        if (AccessToken.IsNullOrWhiteSpace()) await GetNewAccessToken(ct);
        if (_tokenExpiry != DateTime.MinValue && DateTime.UtcNow > _tokenExpiry) await GetNewAccessToken(ct);

        return new ManagementApiClient(AccessToken, _settings.Domain);
    }

    // ReSharper disable once JoinNullCheckWithUsage
    private async Task GetNewAccessToken(CancellationToken ct = default) {
        using var client = new RestClient($"https://{_settings.Domain}");
        var request = new RestRequest("/oauth/token", Method.Post);

        request.AddHeader("Content-Type", "application/x-www-form-urlencoded");

        request.AddParameter("grant_type", "client_credentials", ParameterType.GetOrPost);
        request.AddParameter("client_id", _settings.ClientId, ParameterType.GetOrPost);
        request.AddParameter("client_secret", _settings.ClientSecret, ParameterType.GetOrPost);
        request.AddParameter("audience", $"https://{_settings.Domain}/api/v2/", ParameterType.GetOrPost);
        // request.AddParameter("scope", "read:users update:users read:resource_servers", ParameterType.GetOrPost);

        RestResponse response = await client.ExecuteAsync(request, ct);
        if (!response.IsSuccessful) throw new Exception($"Error: {response.StatusCode} - {response.Content}");
        if(response.Content.IsNullOrWhiteSpace()) throw new Exception("Error: Access token is empty");
        
        // Deserialize JSON response
        var jsonResponse = JsonSerializer.Deserialize<Auth0TokenResponse>(response.Content);
        if (jsonResponse?.AccessToken is null) throw new Exception("Failed to retrieve access token.");
        
        // Store relevant values
        AccessToken = jsonResponse.AccessToken;
        _tokenExpiry = DateTime.UtcNow.AddSeconds(jsonResponse.ExpiresIn);
        _scopes = jsonResponse.Scope;
        _tokenType = jsonResponse.TokenType;
        
    }
}

public class Auth0TokenResponse {
    [JsonPropertyName("access_token")]
    public string? AccessToken { get; set; }

    [JsonPropertyName("expires_in")]
    public int ExpiresIn { get; set; }

    [JsonPropertyName("scope")]
    public string? Scope { get; set; }

    [JsonPropertyName("token_type")]
    public string? TokenType { get; set; }
}

The Issue

Everything works when I use my testing access token in my config file, but as soon as that isn’t defined and I fetch a new access token, things go wrong.

  • The token request succeeds, but in debugging, I noticed that the scope field returns null.
  • When I try to use this access token in a request, I get the following error:
Auth0.Core.Exceptions.ErrorApiException: {"statusCode":401,"error":"Unauthorized","message":"Invalid token","attributes":{"error":"Invalid token"}}

This makes me think that I need to explicitly request scopes when fetching the access token, like in the commented-out line:

request.AddParameter("scope", "read:users update:users read:resource_servers");

But when I try that, I get this error when requesting the token:

Error: Forbidden - {"error":"access_denied","error_description":"Client has not been granted scopes: read:user

I’m not sure how to proceed from here. How do I correctly request and use an access token with scopes for the Management API?

Any help would be greatly appreciated! :blush:

(For clarity sake, the repo with the code snippet I posted above can be found here : GitHub - InfiniLore/credentials.cs: Auto generate permissions )

Ive been struggling on this for a couple of days now, and I fixed the issue…
The issue was that I was using the clientId and client-secret from my “regular web application” and that wasnt working, ofcourse. I switched the clientId and clientSeceret to that of API Explorer Application and everything works nicely, after I configured MyOwnApplication->APIs to enable Auth0 Management API

Have I done this correctly now? The code is working but if there is a better way Ill gladly change my code

Hi @AnnaSasDev

Welcome to the Auth0 Community!

First of all, it is a great idea for the c# library; it’s wonderful to see community members expanding the possibilities of Auth0! Regarding your response, your approach is correct, but if you want to get more granular in terms of permissions gained between your application and Management API, you can expand the scope for the particular application. From the Management API tab, click on → Machine to Machine Applications and select your “MyOwnApplication,” expand it with a down arrow, and select the appropriate scope. After that, you’ll be able to change the clientID and clientSecret back to your original Regular Web Application.

Thanks
Dawid

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.