1
votes

I have a working Web API 2 mobile service hosted in AWS and I want to move to AMS. It works in Postman and on mobile devices just fine.

I followed several blog/posts and spent several hours rewriting and reordering the WebApiConfig.Register. I then created a new AMS project and copied over all my controllers etc. and I had the same result. I reviewed many similar questions but am brain dead over 20 something lines of code.

It works locally through Postman but after I published it I get

HTTP 401 - {"message":"Authorization has been denied for this request."}

Here is the AWS working startup.cs -- I do not call WebApiConfig.Register

namespace Savviety.Data.Service

{ public partial class Startup { public void Configuration(IAppBuilder app) {

        var config = new HttpConfiguration();

        ConfigureOAuth(app);

        // remove in production
        var cors = new EnableCorsAttribute("*", "*", "*");
        config.EnableCors(cors);

        app.UseWebApi(config);

        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );


        var path = AppDomain.CurrentDomain.BaseDirectory + @"\log4net.config";
        var fileInfo = new FileInfo(path);


        XmlConfigurator.ConfigureAndWatch(fileInfo);
        if (fileInfo.Exists)
        {
            log4net.Config.XmlConfigurator.ConfigureAndWatch(fileInfo);
        }
        else
        {
            throw new FileNotFoundException("Could not find log4net.config");
        }

    }

    public void ConfigureOAuth(IAppBuilder app)
    {
        var oAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new SimpleAuthorizationServerProvider()
        };

        // Token Generation
        app.UseOAuthAuthorizationServer(oAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

    }
}

In the AMS version I call the WebApiConfig.Register method from Application.Onstart in Global.asax

 public static void Register( )
    {
        .
        var options = new ConfigOptions();

        var config = ServiceConfig.Initialize(new ConfigBuilder(options));
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // Make sure this is after ServiceConfig.Initialize
        // Otherwise ServiceConfig.Initialize will overwrite your changes
        Microsoft.WindowsAzure.Mobile.Service.Config.StartupOwinAppBuilder.Initialize(appBuilder =>
        {
            ConfigureOAuth(appBuilder);

            appBuilder.UseWebApi(config);

            var path = AppDomain.CurrentDomain.BaseDirectory + @"\log4net.config";
            var fileInfo = new FileInfo(path);

        });
        //var cors = new EnableCorsAttribute("*", "*", "*");
        //config.EnableCors(cors);


        // Web API routes
        // config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }

I also replaced [Authorize] with [AuthorizeLevel(AuthorizationLevel.User)] and removed the startup.cs class.

In Postman it works locally, but not after I publish it. It generates a token, but authentication fails.

TIA

Gary

2

2 Answers

1
votes

The AuthorizeLevel attribute looks for a token issued by Mobile Services. Since you are not actually issuing such a token in the above, it will fail.

Things are probably working locally since the default config makes all local calls accepted. As described here, you will want to go into the Register() method of WebApiConfig.cs and add the following:

config.SetIsHosted(true);

This should cause calls to start failing locally.

To address the core issue, it is possible to wire your own OWIN provider into the Mobile Services pipeline. You will need to create a child class of LoginProvider which basically does your ConfigureAuth() call inside of its ConfigureMiddleware(). Please see the example in this blog post which sets up a LinkedIn middleware.

0
votes

Ok, the primary issue is Azure will not support custom OWIN authentication or I cannot find how to implement it anywhere. I have to use a provided list of users and passwords from another system so it has to be custom.

The solution is a custom LoginController and LoginProvider the relevant code is below.

MyLoginProvider is a subclass of LoginProvider and calls the CreateLoginResult base method.

I had to modify my javascript auth interceptor to config.headers["X-ZUMO-AUTH"] = $localStorage.token; instead of the OAuth bearer token header.

I cannot get the email or display name from the claims identity on a request but I used a work around. When I figure it out I will post it here, but for now it is not blocking me.

public HttpResponseMessage Post(LoginRequest loginRequest)
    {

        var mongoDbManager = MongoDbManager.GetInstance();
        var userCollection = mongoDbManager.GetCollection<UserDocument>(CollectionNames.User);
        var q0 = Query<UserDocument>.EQ(i => i.ClientId, loginRequest.ClientId);
        var q1 = Query<UserDocument>.EQ(i => i.UserEmailAddress, loginRequest.UserName);
        var q2 = Query<UserDocument>.EQ(i => i.UserPassword, loginRequest.Password);
        var query = Query.And(q0, q1, q2);

        var result = userCollection.FindOne(query);

        if (result == null)
        {
            return this.Request.CreateResponse(HttpStatusCode.Unauthorized, "Invalid username or password");
        }
        else
        {
            var claimsIdentity = new ClaimsIdentity();
            claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, result.UserId));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Email, result.UserEmailAddress));
            claimsIdentity.AddClaim(new Claim("DisplayName", result.DisplayName));
            var loginResult = new SavvietyLoginProvider(handler).CreateLoginResult(claimsIdentity, Services.Settings.MasterKey);
            return this.Request.CreateResponse(HttpStatusCode.OK, loginResult);
        }
    }
}