3

I'm having a heck of a time finding anything on this, even though I would think it's pretty straight forward...

I'm developing an API using .NET MVC 4.5. I want the end user to be able to send a request like (PowerShell):

$webclient = new-object System.Net.WebClient
$webclient.Credentials = new-object System.Net.NetworkCredential($username, 
$securePassword)
$doc = $webclient.DownloadString("http://localhost:60023/api/getData?id=200")

But I don't know how to pull the Credentials from the request in my application.

My controller looks like:

namespace App.Controllers
{
    public IEnumerable<MyStruct> Get(int id)
    {
        // TODO: get credentials from request

        // validate credentials
        // code to create IEnumerable<MyStruct>
        return DataList;
    }
}

So far, everything works if I hard-code the credentials, all I need to figure out is how to get the credentials from the end-user's $webclient.Credentials.

I'm sure this has been discussed before, but I've been searching for hours and can't find the answer.

Am I Way Off?

Is this not a good way to go about authenticating API requests? I know that there is an [Authorize] attribute that can use the built-in authentication system, but my app uses custom login verification, so I don't think that will work. Is there a reason verifying credentials within the Controller method in the manner that I want is a bad idea?

JED
  • 1,538
  • 2
  • 19
  • 47
  • 1
    But why the down-vote? A little explanation would be nice... – JED May 15 '18 at 21:32
  • There is a better and secure token based mechanism instead of passing username and password inside header on every request. [Here](https://stackoverflow.com/questions/38661090/token-based-authentication-in-web-api-without-any-user-interface) is an example. *Btw, I did not down vote your question.* – Win May 15 '18 at 23:10

1 Answers1

5

Typically MVC is not used for API work. While MVC can handle serving API calls, Microsoft has an MVC like product called WebApi that is more geared to serving an API, such as returning JSON or XML instead of a view.

In both MVC and WebApi, handling authentication and authorization within methods is not advised. While it works, it opens you up to forgetting to secure an API call. Therefore Microsoft made the AuthenticationFilter and AuthorizationFilter attributes. These allow you to mark which APIs or Views to secure by Method, Class, or Application.

To access basic credentials from a request in MVC, you would access the Authorization header. This will be a string in the form of Basic [Base64Encoded, colon delimited strings].

The following code shows a rough example of how to read the credentials:

public ActionResult TestView()
{
    bool isAuthenticated;
    var base64Header = Request.Headers["Authorization"];
    //The header is a string in the form of "Basic [base64 encoded username:password]"
    if (base64Header != null)
    {
        var authHeader = AuthenticationHeaderValue.Parse(base64Header);
        if (authHeader != null
            && authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase)
            && authHeader.Parameter != null)
        {
            //Decode the username:password pair
            var credentialPair = Encoding.ASCII.GetString(Convert.FromBase64string(authHeader.Parameter));

            //Split into pieces
            var credentials = credentialPair.Split(new [] {":"}, StringSplitOptions.None);
            var userName = credentials[0];
            var plainTextPassword = credentials[1];
            isAuthenticated = SomeAuthenticator.Authenticate(userName, password);
        }
    }
    if (isAuthenticated)
       return Foo();
    else
       RedirectResult("your login view");
}

It would be best to insert this code into an AuthenticationFilter however. This grants you the ability to turn authorization on/off by method, class, or application.

public class CustomAuthFilter : IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        var header = filterContext.RequestContext.HttpContext.Request.Headers["Authorization"];
        if (!Authenticate(header))
            filterContext.Result = new HttpUnauthorizedResult();
    }

    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
    {
       filterContext.Result = new RedirectResult(@"https:\\foo\YourLoginPage");
    }

    private bool Authenticate(string rawAuthorizationHeader)
    {
        try
        {
            if (rawAuthorizationHeader != null)
            {
                var authHeader = AuthenticationHeaderValue.Parse(rawAuthorizationHeader);
                if (authHeader != null
                    && authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase)
                    && authHeader.Parameter != null)
                {
                    var credentialPair = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader.Parameter));
                    var credentials = credentialPair.Split(new[] { ":" }, StringSplitOptions.None);
                    var userName = credentials[0];
                    var plainTextPassword = credentials[1];
                    return SomeAuthenticator.Authenticate(userName, plainTextPassword);
                }
            }

            return false;
        }
        catch (Exception)
        {
            return false;
        }
    }
}

And then consumed by

[CustomAuthFilter] //Secure all methods in the class
public class SecureApiController
{

    public ActionResult SecuredApiCall()
    {
        return Foo();
    }

    public ActionResult AnotherSecuredCall()
    {
        return Bar();
    }

    [AllowAnonymous]
    public ActionResult UnsecuredApiCall()
    {
        return UnsecureFoo();
    }
}

Note that in typical Microsoft fashion, Authentication and Authorization are two different things. You will need to setup an Authorization filter if you want to secure an API by more than "this user has an account".

Anonymous
  • 66
  • 1
  • Thank you for the thorough answer! It's greatly appreciated! – JED May 16 '18 at 01:57
  • Also, I've since found a post regarding adding a web api to an existing MVC project [link](https://stackoverflow.com/questions/11990036/how-to-add-web-api-to-an-existing-asp-net-mvc-4-web-application-project) – JED May 16 '18 at 04:34