0
votes

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:

app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
        {
            SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
            ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
            ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
            TokenHandler = config.GetAppServiceTokenHandler()
        });

Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.

Server project:

  1. Auth controller

    public class ClubrAuthController : ApiController { private readonly ClubrContext dbContext; private readonly ILoggerService loggerService;

    public ClubrAuthController(ILoggerService loggerService)
    {
        this.loggerService = loggerService;
        dbContext = new ClubrContext();
    }
    
    public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
    {
        var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
        if (user == null)
        {
            user = await CreateUser(loginRequest);
        }
    
        var token = GetAuthenticationTokenForUser(user.Email);
    
        return Ok(new
        {
            authenticationToken = token.RawData,
            user = new { userId = loginRequest.username }
        });
    }
    
    private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
    {
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, userEmail)
        };
    
        var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
        var audience = Identifiers.Environment.ApiUrl;
        var issuer = Identifiers.Environment.ApiUrl;
    
        var token = AppServiceLoginHandler.CreateToken(
            claims,
            secretKey,
            audience,
            issuer,
            TimeSpan.FromHours(24)
            );
    
        return token;
    }
    

    }

  2. Startup.cs

        ConfigureMobileAppAuth(app, config, container);
        app.UseWebApi(config);
    }
    
    private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
    {
        config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
    
        app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
        {
            SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
            ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
            ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
            TokenHandler = config.GetAppServiceTokenHandler()
        });
    }
    

Client project:

MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);

Additionally I configured Facebook provider in azure portal like described here. But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs. What I am missing to make both types of authentication works at the same time?

2

2 Answers

0
votes

Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).

app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.

One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.

If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.

The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.


For other folks that find this issue

  • The documentation for custom authentication can be found here.
  • A general overview of App Service Authentication / Authorization can be found here.
-1
votes

The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).

Check out Chapter 2 of my book - http://aka.ms/zumobook