I have a Web API application with MVC. When a user is using the website, the authentication and authorization is currently automatically handled by the global forms authentication I use, configured in the Web.config like so:
<authentication mode="Forms">
<forms loginUrl="~/Login" slidingExpiration="true" timeout="1800" defaultUrl="/"></forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
This makes sure only logged in users can access the site and call the API.
But I also have an external Windows client for which I would like to use another authentication method. In a test without the forms auth, I set up a custom AuthorizeAttribute that I can use in my controllers like this:
[ApiAuth]
public IEnumerable<string> Get() {
// Return the resource
}
The AuthorizeAttribute looks something like this:
public class ApiAuthAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext context) {
// Authenticate the request with a HMAC-based approach
}
}
This works fine in isolation but I cannot figure out how to allow both auth methods. I would like to the ApiAuth as a fallback if the form auth doesn't work (or the reverse, whatever works), but if I apply the [ApiAuth] attribute, only that will be used and normal users cannot access the api.
So, how can I use multiple auth methods, either by using one of them as a fallback if the other one fails, or configuring the server so the Windows client can call the API some other way then the MVC app, while still keeping the same API calls available to both type of clients?
Thank you.
Edit: One approach that I could probably take, is to let the Windows client authenticate using the forms auth (something like this), but it seems very much like a hack and I would much rather use some other approach.
FormAuthentication can be achieve multiple way. In old day, we use FormAuthentication Ticket.
Now, you can use claim-based authentication with Owin Middleware which basically is a strip down version of ASP.Net Identity.
After you authenticate a user inside ApiAuthAttribute, you create Principal object.
Web.config
You should not use
<authorization>
tag in ASP.Net MVC. Instead, you want to use Filter.ApiAuthAttribute
Authentication
Startup.cs
I implemented something similar a while back. You may want to look at third party auth providers (as they have been tested). If you create your own mechanism make sure that whatever data you store to identify an authenticated user session will be removed based on some expiration value.
When I refer to a token below, please note that I am refering to a hash using a combination of :
For example. You could hash the username/hh:mm:ss:ms/fully qualified path/enpoint/enpoint parameters into your user's token. Then you have to decide if the token will be valid on a sliding expiration, 30 minutes, or is it only valid per request.
I would add an anonymous endpoint for your test application to authenticate against. This endpoint should accept user credentials and return a token that matches an entry in Ticket table that represents the user with an expiration. Essentially, since you are not attaching a ticket to each request you will have to manage this yourself in some fashion as I have suggested using the http
authorization header
.Now the client ,your testing app, has been authenticated against an existing set of credentials and has a token representing that handshake.
Your test app now only has to sign the authorization http header with the value returned from get
GetAuthententicationToken().
Now you can implement your
AuthorizeAttribute
in which case you want to validate the authorization header token with what was previously stored with a successful call to your anonymousGetAuthententicationToken
method.So how to handle
FormsAuthentication
with the above scheme in mind?Since Forms Authentication is handled earlier in the request processing than the MVC Authorize you have a perfect opportunity to add your custom authorization header to the incoming request when the user is authenticated via your forms method.
In the same place that you authenticate your forms authentication add something similar to below.
Next, you need to make sure the custom authorization header is applied per request. The best place to do this would be the
Application_AuthenticateRequest
in the Global.asax file.NOTE : The Ticket database table mention above should save a valid authentication request with a datetime stamp for expiration date. You must ensure that you have a process that runs in the background to enforce the timeout by removing expired session records.