Reputation: 1687
I am working on a single page app implemented as static html files making AJAX request to an Web Api web service. It is hosted on an IIS 7.5.
I have problems getting authentication on static html files to work, when users have authenticated themselves against and web api service (sharing the same machine key as the web site).
I basically have a basic web site with static .html files and an asp.net application with the Web Api web service. Most of the static html files should only be available to authenticated users (requests with a valid FormsAuthentication cookie issued by a /api/login route in the web api app), and these files are located in a 'secure' folder below the website root. The Web Api ASP.NET application is located in an 'api' folder below the website root:
WebsiteRoot/
api/
secure/
login.html
1.html
2.html
This works fine, and the static files like 1.html can make AJAX requests to the web api routes below the /api route. Now I want to move 1.html and 2.html inside secure/ to restrict access to them to only authenticated users.
The web api service itself uses FormsAuthentication to restrict access to the service routes. Users authenticate by making AJAX requests to /api/login (which is configured with [AllowAnnonymous]), and now I want only users authenticated this way to be granted access to the static .html files inside the secure/ folder.
So my idea is this:
1) Ensure that the ASP.NET pipeline handles requests to .html files.
2) Enable forms authentication in the static web site
3) Deny anonymous access to the websiteroot/secure folder.
4) Ensure that the Auth cookie issued when authenticating against /api/login is also valid for the authentication mechanism on the /secure folder.
I have tried doing this, but currently I am denied access to files inside websiteroot/secure even though the request is authenticated (it has an .ASPFORMSAUTHI cookie issued by the /api/login route in the web api app)
Here is what I have done: I have 3 web.config files: 1 in WebsiteRoot, 1 in WebsiteRoot/api (the root of the web api application) and 1 in WebsiteRoot/secure
To ensure 4), I have configured FormsAuthentication identically and used the same machoine key in both the website root and the websiteroot/api web.config files. The domin for the cookie is set to the same in both files.
Then, to ensure 1), I have added this to the website root web.config file, to ensure that .html files (all files) are handled and authenticated by the ASP.NET pipeline:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />
<remove name="UrlAuthorization" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
</modules>
</system.webServer>
Here is the relevant stuff from the web api application web.config file located in websiteroot/api/ :
<system.web>
<authentication mode="Forms">
<forms name=".ASPXFORMSAUTH" protection="All" path="/" domain=".mydomain.com" timeout="7200" slidingExpiration="true" />
</authentication>
<machineKey validationKey="SAME_IN_BOTH_FILES" decryptionKey="SAME_IN_BOTH_FILES" validation="SHA1" decryption="AES" />
<roleManager enabled="true" defaultProvider="simple">
<providers>
<clear />
<add name="simple" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
<membership defaultProvider="simple">
<providers>
<clear />
<add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
...
Here is the website root web.config file located in websiteroot/ :
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="login.html" name=".ASPXFORMSAUTH" protection="All" domain=".mydomain.com" path="/" timeout="7200" slidingExpiration="true" />
</authentication>
<machineKey validationKey="SAME_IN_BOTH_FILES" decryptionKey="SAME_IN_BOTH_FILES" validation="SHA1" decryption="AES" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />
<remove name="UrlAuthorization" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
</modules>
</system.webServer>
</configuration>
Here is the web.config file located in websiteroot/secure/ :
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
Note, that if I change
<deny users="?" />
to
<allow users="?" />
Then I am granted access to the .html files inside websiteroot/secure/
So my question are:
1) Why are requests to /secure denied when the requests contains the .ASPFORMSAUTH cookies issued by /api/login?
2) Are there alternative solutions as to how to make a static website where some files are restricted to users who have been authenticated against an web api service on the same site?
Upvotes: 2
Views: 3526
Reputation: 27236
I'm sure you already realize, but the problem you're facing has to do has to do with being sessionless in a true WebAPI environment. Forms Authentication is based on the session cookie being saved on your client side, and interpreted by your server-side on each subsequent request.
If you're authenticating via a WebAPI call, you will need to handle the creation / reading of a cookie directly. One of the main benefits of using this kind of truly RESTful implementation is that you shouldn't really have a "session" per-se.
In my applications, one of the ways in which I handle this is to use data keys which uniquely identify my user's identity - and then store it in a cookie on the client-side, encrypted in a way that only my server can understand. I won't get into the security requirement pieces here - because there are better forums for you to take a look at regarding how to encrypt/decrypt, etc. - but the basic deal is this:
Controller
objects which knows how to interpret that cookieThen, do one of the following:
Either 1) Create a message handler for your static content which also handles authenticating based on the cookie you set - and checks for permissions to the static files...
OR 2) Create a new Controller
which has an action which will serve your static content. This has the benefit of being able to inherit from your base controller class and gets the authentication there from step 2. for free.
Upvotes: 4