API for Xamarin app - getting nowhere

I have built an API using .NET Core 3.1 for a Xamarin app. I have previously used Auth0 with an ASP.NET site.

I registered an Application (native) and and API in the Auth0 dashboard. I followed the instructions for adding the libraries and the code to Startup.cs. When I add [Authorize] to the controllers, but don’t add any authorization header, I get a 401 error from the API requests, which I would expect.

I have the Xamarin app logging in and getting a bearer token from Auth0. I am also able to log in using the Authentication API Debugger and get a bearer token that is very similar in appearance.

The Xamarin app makes an API GET call using the bearer token and still gets a 401 error.

I have tried to access the API with Postman and the reply header contains WWW-Authenticate - Bearer error=“invalid_token”

I have tried to debug the API running on Localhost using Postman with the same result.

I am missing something but I have no idea what. I would really appreciate any clues.

Thanks in advance,

Rob

Hi @rlkeith,

Thanks for reaching out to the Auth0 Community!

I understand that you encountered errors when using your token, specifically with the 401 Unauthorized error.

In this situation, could you please check your access token on jwt.io and see if the payload contains the values you expect?

If not, could you please share your complete /authorize and /oauth/token requests with me?

See here for details.

Looking forward to your reply.

Thank you.

Hi @rueben.tiow

Thanks for the reply.

I have been a developer for many years but I am not familiar with this field at all. Please excuse the massive gaps in my knowledge. We have lost the developer who was doing this and I am looking for someone else. Meanwhile I need to get this working one way or another.

For this application we have an app configured (Native) with the audience and callback all working. It is getting a valid token from Auth0. The Xamarin code is:

namespace AnnoLog.Features.Login
{
    public class LoginViewModel : BaseViewModel
    {
        private readonly IAnnoLogService annoLogService;
        private readonly IAuthService authenticationService;
        public ICommand Login { get; }

        public LoginViewModel(IDependencyService dependencyService) : base(dependencyService)
        {
            annoLogService = DependencyService.Get<IAnnoLogService>();
            authenticationService = DependencyService.Get<IAuthService>();

            this.Login = ReactiveCommand.CreateFromTask(async () =>
                {
                    this.IsLoading = true;
                    var authenticationResult = await authenticationService.Authenticate();

                    if (!authenticationResult.IsError)
                    {
                        var accessToken = authenticationResult.AccessToken;
                        var claims = authenticationResult.UserClaims.GroupBy(c => c.Type).Select(c => c.First()).ToDictionary(c => c.Type, c => c.Value);
                        ;//token is available here

It retrieves a token that looks valid when pasted into jwt.io. The

  "iss": "https://dev-7cdpp4yt.us.auth0.com/",
  "sub": "auth0|61e41b9f690cd100686f9240",
  "aud": [
    "https://rklogintest.azurewebsites.net/",
    "https://dev-7cdpp4yt.us.auth0.com/userinfo"
  ],
  "iat": 1644493115,
  "exp": 1644579515,
  "azp": "JX2j0SonmwEu0yIUH6TQn5WaIOQO1bkZ",
  "scope": "openid profile email"
}

This all looks fine, and at this point it gets weird.

We have an API configured, but because the app is Native, it is not in the Machine to Machine Applications tab. There is also no Permissions set in the Permissions tab because I have no idea how to configure them or if they are needed. I can see nothing to connect the configured App with the configured API.

The API was built on .NET 4.7.2 so I put all the API functions into a DLL project and created a .NET Core API to call the functions. This works well when authentication is not enabled.

In accordance with the instructions in the API, the startup.cs, which is pasted below.

I am sure something is missing.

I also posted another message to this forum yesterday. I created an entirely new API but the results are exactly the same.

Thanks,

Rob

Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AnnoLogAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers()
                   .AddNewtonsoftJson();      //this is required because the standard Microsoft JSON will not serialize the output from SpudData
            //services.AddMvc()
            //    .AddNewtonsoftJson();
            //1.Add Authentication Services
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.Authority = "https://dev-7cdpp4yt.us.auth0.com/";

                //options.Audience = "https://localhost:44369/api/";
                options.Audience = "https://rklogintest.azurewebsites.net/";
            });

            services.AddControllers(o => o.InputFormatters.Insert(o.InputFormatters.Count, new TextPlainInputFormatter()));

            services.AddMvc(options =>
            {
                options.EnableEndpointRouting = false;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //else
            //{
            //    app.UseExceptionHandler("/Home/Error");
            //}

            app.UseHttpsRedirection();

            app.UseRouting();

            //app.MapControllerRoute(
            //    name: "default",
            //    pattern: "{controller=Home}/{action=Index}/{id?}");

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseStaticFiles();

            // 2. Enable authentication middleware
            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                  name: "default",
                  template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

1 Like

Hi @rueben.tiow Is there any other information you need to help with this problem? My project is at a standstill and my colleagues are losing their patience.

I am convinced that this is a trivial problem and that I am missing something. I would happily pay for someone with the necessary knowledge to help me get it sorted out.

Rob

1 Like

The access token looks fine, it might be an issue in your .NET Core API. I assume the ordering of your middlewares might be incorrect. Can you move “UseAuthentication” higher up, before “UseAuthorization”?

You can find a working example here: JWT Bearer Authentication and Authorization for ASP.NET Core 5 – sandrino.dev

1 Like

Hi @sandrino-a0,

Thanks for that information. This also relates to another problem being addressed by @rueben.tiow

I don’t want to cause a duplication of effort.

Thanks,

Rob

Hi again @sandrino-a0

Your example is finally something I can understand. Thank you! I will implement it in my API and see how I go.

Rob

Hi again @sandrino-a0

I have added the code from your example into my project. It works (fails to work) the same as before but is not a bit easier to debug.

A couple of questions:

Does “Audience”: “urn:my-api” mean that I can use localhost to debug the API. That would make debugging the API a lot easier.

I have a native app and an API configured. The token is obtained using the login from the native app but sent to the API. There is no relationship that I can see to bind them together. Should there be a relationship? Could that be the cause of my problems.

Thanks and regards,

Rob

When you define your API in the Auth0 dashboard you are asked to fill in an identifier. In my case I entered “urn:my-api”. The exact value of the identifier field is what you need to use as the “Audience” in your .NET API.

1 Like

Could you please share:

  • The code you’re writing to call the API
  • The last version of your Startup.cs file
2 Likes

Hi @sandrino-a0 Sorry I haven’t replied yet. I got caught up on other stuff.

I am starting to suspect that my Xamarin app is not getting the tokens properly, or not getting the right tokens. I have been looking at your blog and it has lots of interesting stuff. Do you have an example of something that can log in and get tokens? If not I will build a .NET app to do it so I can debug things properly.

1 Like

Hi again @sandrino-a0,

The Xamarin app seems to be fine. Here is the code it uses to log in and get the token and claims:

using System.Threading.Tasks;
using AnnoLog.Common;
using AnnoLog.Droid.Services;
using Auth0.OidcClient;
using IdentityModel.OidcClient.Browser;
using Xamarin.Forms;
using static AnnoLog.Common.GlobalInfo;

[assembly: Dependency(typeof(AuthenticationService))]
namespace AnnoLog.Droid.Services
{
    public class AuthenticationService : IAuthService
    {
        private Auth0Client _auth0Client;

        public AuthenticationService()
        {
            _auth0Client = new Auth0Client(new Auth0ClientOptions
            {
                Domain = AuthConfig.Domain,
                ClientId = AuthConfig.ClientId
            });
        }
        public AuthenticationResult AuthenticationResult { get; private set; }

        public async Task<AuthenticationResult> Authenticate()
        {
            var auth0LoginResult = await _auth0Client.LoginAsync(new { audience = AuthConfig.Audience });
            AuthenticationResult authenticationResult;

            if (!auth0LoginResult.IsError)
            {
                authenticationResult = new AuthenticationResult()
                {
                    AccessToken = auth0LoginResult.AccessToken,
                    IdToken = auth0LoginResult.IdentityToken,
                    UserClaims = auth0LoginResult.User.Claims
                };
            }
            else
            {
                authenticationResult = new AuthenticationResult(auth0LoginResult.IsError, auth0LoginResult.Error);
            }
            AuthenticationResult = authenticationResult;
            return authenticationResult;
        }

I changed my startup.cs file according to your example.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace WebApplication1
{
    public class Startup
    {
        public IConfiguration _configuration;
        
        public Startup(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            // Configure JWT authentication.
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
              .AddJwtBearerConfiguration(
                _configuration["Jwt:Issuer"],
                _configuration["Jwt:Audience"]
              );
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseStaticFiles();
        }
    }
}

If I configure an API with the audience as localhost:port is it likely to work? I will try it anyway. One problem I am having is being able to debug the API.

Regards,

Rob

1 Like

The value for the API Identifier (Audience) is just a URI. It can be anything, typically the URL of your API. We don’t call that URI, it is added to the access token and then used by your API as part of the token validation process.

Could you show the Xamarin code you’re using to call your .NET API? Just to make sure the token is passed along correctly.

To easily get an access token outside of your app you could use ROPG: Call Your API Using Resource Owner Password Flow (you can use Postman to get the token, and then use Postman to call your API with that token). If that works, I would suggesting walking back and trying to figure out what is wrong in your Xamarin application.

Debugging is a shortcoming in .NET itself as it doesn’t support sufficient context as to why the token validation failed.

Other things worth looking into:

  • The API Identifier needs to be the same value as what you use in Xamarin (AuthConfig.Audience) and you API (Jwt:Audience)
  • The issuer needs to be configured correctly on your API (Jwt:Issuer): https://dev-7cdpp4yt.us.auth0.com/
  • In the API, under TokenValidationParameters you could try to turn each on of these settings to “false” to try and pinpoint the issue better:
ValidateIssuer = false
ValidateAudience = false
ValidateLifetime = false
ValidateIssuerSigningKey = false

(remember to turn these back on once you’ve found the issue)

1 Like

Thank you for the extra information. The code used by the Xamarin app is:

               string userDevicesUrl = $"{GlobalInfo.UrlAPI}/AdeviceList/{savedAuid}";     //Changed to match the new API 220208

                string accessToken = await SecureStorage.GetAsync("accessToken");
                ;
                HttpClient http = new HttpClient();
                http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                
                HttpResponseMessage res = await http.GetAsync(userDevicesUrl).ConfigureAwait(false);
               
                res.EnsureSuccessStatusCode();

I had a guy in the Philippines helping me with the Xamarin app, but he is not often available because his home was flattened by a typhoon. He has looked at the problem and can’t see why it isn’t working.

I will try your suggestions. Is using Postman to hit the API, with the bearer token, a legitimate way to troubleshoot it?

Thanks,

Rob

1 Like

Hi @sandrino-a0 I got it working.

Your code example was the key. Also getting the audience right is crucial.

It turns out that using Postman with the API running on Localhost is perfectly possible. I was able to find a bug in my code.

Thanks,

Rob

1 Like

That’s great to hear - happy this got solved. Could you share more details about the bug you found? This will be helpful to improve our docs and for the community to learn from it.

1 Like

Hi @sandrino-a0 it was just a bug in the C# code that I had edited. It caused an error and the API returned empty {}.

Also, understanding the difference between the access token and the ID token was a basic newbie error.

Rob

2 Likes