Use Active Directory with Web API for SPA

Lukas Pirkl picture Lukas Pirkl · Dec 9, 2013 · Viewed 22.9k times · Source

I am building single page application and I would like to know user's identity. We have Active Directory in our intranet but I don't know much about it. I am able to use code like this to verify username and password.

using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
  bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

In fact that is everything I need from the Active Directory side of thing. So I could create some AuthorizationFilter with this code but this would mean that username and password must be in every request. I would like to send username and password only once and then use some token for authorization. So there should be some token provider inside my server-side application.

I am building this application from scratch so I can use the latest .net technologies. I found that there are some Owin middlewares which are handling tokens, cookies and OAuth. Could anything of that help me?

Answer

Lukas Pirkl picture Lukas Pirkl · Dec 10, 2013

When I was searching for something completely different, I found this incredible project on CodePlex: https://angularapp.codeplex.com/ It is exactly what I was looking for.

Update:

The part of this app which was useful to me is the DomainUserLoginProvider

public class DomanUserLoginProvider : ILoginProvider
{
    public bool ValidateCredentials(string userName, string password, out ClaimsIdentity identity)
    {
        using (var pc = new PrincipalContext(ContextType.Domain, _domain))
        {
            bool isValid = pc.ValidateCredentials(userName, password);
            if (isValid)
            {
                identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, userName));
            }
            else
            {
                identity = null;
            }

            return isValid;
        }
    }

    public DomanUserLoginProvider(string domain)
    {
        _domain = domain;
    }

    private readonly string _domain;
}

LoginProvider is used in AccountController's action to verify the credentials. Also the AccessToken is created here.

[HttpPost, Route("Token")]
public IHttpActionResult Token(LoginViewModel login)
{
    ClaimsIdentity identity;

    if (!_loginProvider.ValidateCredentials(login.UserName, login.Password, out identity))
    {
        return BadRequest("Incorrect user or password");
    }

    var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
    var currentUtc = new SystemClock().UtcNow;
    ticket.Properties.IssuedUtc = currentUtc;
    ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));

    return Ok(new LoginAccessViewModel
    {
        UserName = login.UserName,
        AccessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket)
    });
}

Last but not least, add the correct middleware to Owin pipeline.

public partial class Startup
{
    static Startup()
    {
        OAuthOptions = new OAuthAuthorizationServerOptions();
    }

    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static void ConfigureAuth(IAppBuilder app)
    {
        app.UseOAuthBearerTokens(OAuthOptions);
    }
}

On the client side you just send the request with credentials to the Token action of AccountController and you'll get the authentication token. Save this token in browser ("Remember me" feature) and set the Angular's $http service to attach token to each request.

services.factory('$auth', ['$q', '$http', '$path', function ($q, $http, $path) {       
    var tokenUrl = $path('api/Account/Token');

    function setupAuth(accessToken, remember) {
        var header = 'Bearer ' + accessToken;
        delete $http.defaults.headers.common['Authorization'];
        $http.defaults.headers.common['Authorization'] = header;
        sessionStorage['accessToken'] = accessToken;
        if (remember) {
            localStorage['accessToken'] = accessToken;
        }
        return header;
    }

    var self = {};

    self.login = function(user, passw, rememberMe) {
        var deferred = $q.defer();
        $http.post(tokenUrl, { userName: user, password: passw })
            .success(function (data) {
                var header = setupAuth(data.accessToken, rememberMe);
                deferred.resolve({
                    userName: data.userName,
                    Authorization: header
                });
            })
            .error(function() {
                deferred.reject();
            });

        return deferred.promise;
    };

    return self;
}]);