ASP.NET MVC5/AngularJS/Web API app using Windows Authentication and OWIN

mmoreno79 picture mmoreno79 · Nov 6, 2014 · Viewed 14.7k times · Source

I may be over-complicating things, but we have an internal ASP.NET MVC5 SPA with AngularJS using Windows Authentication. This application has a SQL back-end database that has a table of users, containing their account names, and their respective roles in the application. We will be making calls to another Web API application that also has Windows Authentication enabled.

I have tried to do research on how to handle authorization using OWIN but couldn't find any specific examples regarding OWIN and Windows Authentication. Everything that turns up uses forms authentication with a username and password.

How can I go about using OWIN and Windows Auth for my app? Here's a sample of my OAuthAuthorizationServerProvider class.

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
        return;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        var container = UnityHelper.GetContainerInstance("***");
        var securityHelper = container.Resolve<ISecurityHelper>();

        User currentUser = securityHelper.GetCurrentUser(); // Validates user based on HttpContext.Current.User
        if (currentUser == null)
        {
            context.SetError("invalid_grant", "The user could not be found.");
            return;
        }

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", currentUser.AccountName));
        identity.AddClaim(new Claim("role", "user"));

        context.Validated(identity);
    }
}

UPDATE: Oops, I forgot to include more information on what we'd like to accomplish. If possible, we'd like to use bearer authentication tickets so we don't have to look up the user and their roles everytime we make a call to a web api method.

UPDATE 2: As requested by Andrew, below is the TLDR version of my _securityHelper class, specifically the GetCurrentUser() method. You'll notice that I'm attempting to call:

HttpContext.Current.GetOwinContext().Request.User.Identity.Name

This always returns null for User.

public class SecurityHelper : ISecurityHelper
{
    private readonly ISecurityGroupController _securityGroupController;
    private readonly IUserController _userController;
    private readonly IEmployeeController _employeeController;
    private readonly IFieldPermissionController _fieldPermissionController;
    private readonly IOACController _oacController;

    public SecurityHelper(ISecurityGroupController securityGroupController,
        IUserController userController,
        IEmployeeController employeeController,
        IFieldPermissionController fieldPermissionController,
        IOACController oacController)
    {
        _securityGroupController = securityGroupController;
        _userController = userController;
        _employeeController = employeeController;
        _fieldPermissionController = fieldPermissionController;
        _oacController = oacController;
    }

    // ... other methods

    public User GetCurrentUser()
    {
        User user = _userController.GetByAccountName(HttpContext.Current.GetOwinContext().Request.User.Identity.Name);
        if (user != null)
        {
            List<OAC> memberships = _oacController.GetMemberships(user.SourceId).ToList();
            if (IsTestModeEnabled() && ((user.OACMemberships != null && user.OACMemberships.Count == 0) || user.OACMemberships == null))
            {
                user.OACMemberships = memberships;
            }
            else if (!IsTestModeEnabled())
            {
                user.OACMemberships = memberships;
            }
        }

        return user;
    }
}

Answer

Claies picture Claies · Nov 7, 2014

This article series would be a good place to start: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

of note, would be the following code, which essentially stores the bearer token in local storage and attaches it to the headers. There is obviously a lot more to it than this, including the forms and the actual server authentication system, but this should give you a decent start.

server component:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureOAuth(app);
    //Rest of code is here;
    }

    public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions 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());

    }
}

and the following client side code:

'use strict';
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {

    var serviceBase = 'http://ngauthenticationapi.azurewebsites.net/';
    var authServiceFactory = {};

    var _authentication = {
        isAuth: false,
        userName : ""
    };

    var _saveRegistration = function (registration) {

        _logOut();

        return $http.post(serviceBase + 'api/account/register', registration).then(function (response) {
            return response;
        });

    };

    var _login = function (loginData) {

        var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password;

        var deferred = $q.defer();

        $http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {

            localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });

            _authentication.isAuth = true;
            _authentication.userName = loginData.userName;

            deferred.resolve(response);

        }).error(function (err, status) {
            _logOut();
            deferred.reject(err);
        });

        return deferred.promise;

    };

    var _logOut = function () {

        localStorageService.remove('authorizationData');

        _authentication.isAuth = false;
        _authentication.userName = "";

    };

    var _fillAuthData = function () {

        var authData = localStorageService.get('authorizationData');
        if (authData)
        {
            _authentication.isAuth = true;
            _authentication.userName = authData.userName;
        }

    }

    authServiceFactory.saveRegistration = _saveRegistration;
    authServiceFactory.login = _login;
    authServiceFactory.logOut = _logOut;
    authServiceFactory.fillAuthData = _fillAuthData;
    authServiceFactory.authentication = _authentication;

    return authServiceFactory;
}]);

along with

'use strict';
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {

    var authInterceptorServiceFactory = {};

    var _request = function (config) {

        config.headers = config.headers || {};

        var authData = localStorageService.get('authorizationData');
        if (authData) {
            config.headers.Authorization = 'Bearer ' + authData.token;
        }

        return config;
    }

    var _responseError = function (rejection) {
        if (rejection.status === 401) {
            $location.path('/login');
        }
        return $q.reject(rejection);
    }

    authInterceptorServiceFactory.request = _request;
    authInterceptorServiceFactory.responseError = _responseError;

    return authInterceptorServiceFactory;
}]);