Marco Castro
Marco Castro

Reputation: 111

How to create custom iPrincipal in MVC 4, WinAPI

I'm experiencing a situation that I have find nowhere in other articles. I'm designing a RESTful server to be consumed by an mobile app. In this case, username and password are part of header in app call, there is no logon screen.

The following code does the job of validating user info and the controllers has security controlled.

My question is: how can I populate iPrincipal in the ApiController controllers?

I have created a filter addressed at WebApiConfig.cs

public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Filtro de login
        config.Filters.Add(new tbAuthorize());

The code for tbAuthorize is:

    public class tbAuthorize : AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        string username;
        string password;
        if (GetUserNameAndPassword(actionContext, out username, out password))
        {
            if (!isUserAuthorized(username, password))
                return false;
            else
            {
                //Users = username;
                return true;
            }
        }
        else
            return false;
    }

    private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
    {
        username = "";
        password = "";
        if (actionContext.Request.Headers.Authorization == null) return false;
        // Convert 64 code to separated string[]
        string[] s = ParseAuthHeader(actionContext.Request.Headers.Authorization.ToString());
        if (s == null)
            return false;

        username = s[0];
        password = s[1];
        return true;
    }

    private string[] ParseAuthHeader(string authHeader)
    {
        // Check this is a Basic Auth header
        if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;

        // Pull out the Credentials with are seperated by ':' and Base64 encoded
        string base64Credentials = authHeader.Substring(6);
        string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });

        if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;

        // Okay this is the credentials
        return credentials;
    }

    private bool isUserAuthorized(string username, string password)
    {
        // Valid the user at database
        var userId = new UsersController().Login(username, password);
        // Membership.GetUser() is null
        //Users = Membership.GetUser().Email;
        return userId != 0;
    }

}

The issue is that I have no access to a cookie in Response and I did not find to way to populate iPrincipal.

I need to has data in this.User.Identity.Name, as this:

    [tbAuthorize]
public class UsersController : ApiController
{
    public void test()
    {
        string x = this.User.Identity.Name;
    }

Thanks for any help,

Marco Castro

Upvotes: 3

Views: 771

Answers (1)

meziantou
meziantou

Reputation: 21337

Authentication and Authorization are two differents things. Before authorizing a user you have to authenticate their.

With WebApi you have the concept of pipeline with Delegatinghandler. Message goes from one handler to the next until one send the response. I recommend you to create a DelegatingHandler to authentificate users. Then you can use AuthorizeAttribute to prevent unauthenticated user to access your API.

Here's an example to authenticate user with HTTP Basic

public abstract class BasicAuthMessageHandler : DelegatingHandler 
{ 
    private const string BasicAuthResponseHeader = "WWW-Authenticate"; 
    private const string BasicAuthResponseHeaderValue = "Basic Realm=\"{0}\""; 

    protected BasicAuthMessageHandler() 
    { 
    } 
    protected BasicAuthMessageHandler(HttpConfiguration httpConfiguration) 
    { 
        InnerHandler = new HttpControllerDispatcher(httpConfiguration); 
    } 

    protected virtual string GetRealm(HttpRequestMessage message) 
    { 
        return message.RequestUri.Host; 
    } 

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
                                                           CancellationToken cancellationToken) 
    { 
        // Process request 
        AuthenticationHeaderValue authValue = request.Headers.Authorization; 
        if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) && 
            string.Equals(authValue.Scheme, "basic", StringComparison.OrdinalIgnoreCase)) 
        { 
            // Try to authenticate user 
            IPrincipal principal = ValidateHeader(authValue.Parameter); 
            if (principal != null) 
            { 
                request.GetRequestContext().Principal = principal; 
            } 
        } 
        return base.SendAsync(request, cancellationToken) // Send message to the InnerHandler
                .ContinueWith(task => 
                { 
                    // Process response 
                    var response = task.Result; 
                    if (response.StatusCode == HttpStatusCode.Unauthorized && 
                        !response.Headers.Contains(BasicAuthResponseHeader)) 
                    { 
                        response.Headers.Add(BasicAuthResponseHeader, 
                                             string.Format(BasicAuthResponseHeaderValue, GetRealm(request))); 
                    } 
                    return response; 
                }, cancellationToken); 
    } 

    private IPrincipal ValidateHeader(string authHeader) 
    { 
        // Decode the authentication header & split it 
        var fromBase64String = Convert.FromBase64String(authHeader); 
        var lp = Encoding.Default.GetString(fromBase64String); 
        if (string.IsNullOrWhiteSpace(lp)) 
            return null; 
        string login; 
        string password; 
        int pos = lp.IndexOf(':'); 
        if (pos < 0) 
        { 
            login = lp; 
            password = string.Empty; 
        } 
        else
        { 
            login = lp.Substring(0, pos).Trim(); 
            password = lp.Substring(pos + 1).Trim(); 
        } 
        return ValidateUser(login, password); 
    } 

    protected abstract IPrincipal ValidateUser(string userName, string password); 
} 

Write you own user validation logic. For example:

public class SampleBasicAuthMessageHandler : BasicAuthMessageHandler 
{ 
    protected override IPrincipal ValidateUser(string userName, string password) 
    { 
        if (string.Equals(userName, "Meziantou", StringComparison.OrdinalIgnoreCase) && password == "123456") 
            return new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[0]); 
        return null; 
    } 
} 

Finally you have to register the Handler

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

    config.MessageHandlers.Add(new SampleBasicAuthMessageHandler()); 

You'll find a complete example on Github: https://github.com/meziantou/Samples/tree/master/Web%20Api%20-%20Basic%20Authentication

Upvotes: 1

Related Questions