0

I'm building an ASP.NET OWIN-based website (essentially an AngularJs SPA app). The solution can be found here:

https://github.com/dsidirop/PetTrackerOAuth/tree/ffa702c6ffa05fb44b332d1961eb8337da0acdae

The solution consists of two projects: one project hosting OWIN/OAuth/WebAPI2 and another one which hosts the SPA (.html, .css, .js files). Both projects have been tweaked to output their files in the same output directory. Thus index.html and the entire folder structure holding .js/.css files ends up in the /bin directory right next to all the .dlls of the OWIN project. My Startup.cs looks like this:

public sealed class Startup
{
    public void Configuration(IAppBuilder appBuilder) //0
    {
        var webApiConfiguration = new HttpConfiguration();

        ConfigureOAuth(appBuilder);

        var physicalFileSystem = new PhysicalFileSystem(@".\bin");
        var options = new FileServerOptions { EnableDefaultFiles = true, FileSystem = physicalFileSystem };
        options.StaticFileOptions.FileSystem = physicalFileSystem;
        options.StaticFileOptions.ServeUnknownFileTypes = true;
        options.EnableDirectoryBrowsing = true;
        options.DefaultFilesOptions.DefaultFileNames = new[] { "index.html" };
        appBuilder.UseFileServer(options);

        WebApiConfig.Register(webApiConfiguration);
        appBuilder.UseCors(CorsOptions.AllowAll);
        appBuilder.UseWebApi(webApiConfiguration); //1

        appBuilder.UseNinjectMiddleware(() => NinjectConfig.CreateKernel.Value).UseNinjectWebApi(webApiConfiguration);
    }
    //0 the app parameter is an interface to a builder instance which is be used to compose the application for our owin server
    //1 the userwebapi extension method is responsible for wiring up the aspnet web api to our owin server pipeline

    public void ConfigureOAuth(IAppBuilder app)
    {
        app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions {
            Provider = new SimpleAuthorizationServerProvider(),
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            RefreshTokenProvider = new SimpleRefreshTokenProvider(),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30)
        });
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

My web.config looks like so:

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      For more information on how to configure your ASP.NET application, please visit
      http://go.microsoft.com/fwlink/?LinkId=301879
      -->
    <configuration>
      <configSections>

        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
      <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
      <appSettings></appSettings>
      <system.web>
        <compilation debug="true" targetFramework="4.6.1" />
        <httpRuntime targetFramework="4.6.1" />
        <httpModules>
        </httpModules>
      </system.web>
      <system.webServer>
        <handlers>
          <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
          <remove name="OPTIONSVerbHandler" />
          <remove name="TRACEVerbHandler" />
          <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
        <validation validateIntegratedModeConfiguration="false" />
        <modules>
        </modules>
        <security>
          <requestFiltering>
            <hiddenSegments>
              <remove segment="bin" />
            </hiddenSegments>
          </requestFiltering>
        </security>
      </system.webServer>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Cors" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Http.WebHost" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Http.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
          </dependentAssembly>
          <dependentAssembly>
            <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
            <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
      <entityFramework>
        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
        <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
        </providers>
      </entityFramework>
      <!-- http://www.asp.net/identity/overview/features-api/best-practices-for-deploying-passwords-and-other-sensitive-data-to-aspnet-and-azure        -->
      <!--                                                                                                                                              -->
      <!-- The configsource attribute replaces the entire <connectionstrings> markup  Thus it doesnt leave any room for merging with existing           -->
      <!-- attributes in the connectionstring element The external connection strings file must be in the same directory as the root web.config         -->
      <!-- file so provisions must be in place to ensure you dont check it into your source repository                                                  -->
      <!--                                                                                                                                              -->
      <!-- Using the configsource attribute as shown below prevents visual studio from detecting that the project is using a database when creating     -->
      <!-- a new web site and thus you wont get the option of configuring the database when you publishing to azure from visual studio                  -->
      <!--                                                                                                                                              -->
      <!-- <connectionStrings><add name="PetTrackerContext" connectionString="provider connection string=&quot;user id=SQLDBUSER;password=PASSWORD;data source=pettracker2016.database.windows.net;initial catalog=PetTracker;persist security info=True;MultipleActiveResultSets=True;App=EntityFramework&quot;;metadata=res://*/Models.PetTrackerModel.csdl|res://*/Models.PetTrackerModel.ssdl|res://*/Models.PetTrackerModel.msl;provider=System.Data.SqlClient;" providerName="System.Data.EntityClient" /></connectionStrings> -->
      <connectionStrings configSource="ConnectionStrings.config">
      </connectionStrings>
      <system.codedom>
        <compilers>
          <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
          <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
        </compilers>
      </system.codedom>
    </configuration>

I've tried to enrich the Startup.cs file using virtually any approach under the sun I could get my hands on in order to have index.html load properly (I'm not using angular in html5 mode btw). However even though index.html gets displayed upon launching the server, all the other resources (.css, .js files) fail to load with a message-spam "404 Resources not found" showing up in the console of the developer-tools of the browser. Some things I've tried:

How to set default static web page for Katana/Owin self hosted app?

https://docs.asp.net/en/latest/fundamentals/static-files.html

How to intercept 404 using Owin middleware

Nothing I've tried so far has worked. So how can one make index.html load properly (with .js/.css resources and all) under OWIN? Any code snippets which resolve this issue cleanly will be highly appreciated. Thanks in advance and sorry if I missed something obvious.

Community
  • 1
  • 1
XDS
  • 3,786
  • 2
  • 36
  • 56

1 Answers1

1

IIS blocks access to the "bin" folder by default as a security mechanism because otherwise you could have malicious actors downloading your compiled site DLLs.

If you really want to allow it, you need to remove "bin" from the list of filtered paths by adding the following to the web.config:

<?xml version="1.0"?>
<configuration>
   <system.webServer>
       <security>
          <requestFiltering>
               <hiddenSegments>
                   <remove segment="bin" />
               </hiddenSegments>
           </requestFiltering>
       </security>
   </system.webServer>
</configuration>

See: http://weblogs.asp.net/owscott/iis7-blocks-viewing-access-to-files-in-bin-and-other-asp-net-folders

I would caution against this approach since it does impact security and would instead just have your SPA folders/files in the root of the website while your compiled resources go into the bin subfolder.

David Archer
  • 2,121
  • 1
  • 15
  • 26
  • Thanks for the tip. I gave it a spin but still no joy :( Do I have to make any particular changes to startup.cs for this to work? – XDS Jun 24 '16 at 13:41
  • I'm using the latest and greatest IIS10.0 Express btw via Visual Studio Community Edition 2015 (if it makes any difference). – XDS Jun 24 '16 at 13:54
  • I cloned your GitHub repo, moved all the files (except web.config) from the PetTrackerOAuth.Web project into the AngularJSAuthentication.API project (and remove the copy to output folder flag on all of them). The site comes up and all the SPA files load fine: http://imgur.com/msR7HOs – David Archer Jun 25 '16 at 19:33
  • I also added the web.config setting I mentioned in my answer and copied one of the .js files into the bin folder manually and was able to pull it up properly. http://imgur.com/vn6BVFK – David Archer Jun 25 '16 at 19:36
  • Thank you for your efforts David. I tried your approach and it indeed works. I guess that there is some sort of magic going on under the hood, in the sense that if a .css/.js/.whatever file is not *explicitly* listed inside the .csproj file of the ASP.NET project then this file is *not* considered to be a resource that can be handed-over http (even if said file has been copied over in some way and thus *is* physically present in the /bin directory). Can OWIN be tweaked to act as static-file http-server for more than just index.html? (so that I can keep the two projects separate). Thx again. – XDS Jun 26 '16 at 00:32
  • Shouldn't be any kind of magic -- IIS isn't going to have access to your csproj after you've compiled and deployed the site so it wouldn't be something there. Can you just change the client-side JS project to output to one level up from the "/bin" folder where the DLLs are? Seems like that would still let you have the projects separate. – David Archer Jun 27 '16 at 00:49
  • Your suggestion would probably work but spawning files like that on the parent directory of /bin feels a bit murky. I guess I'm out of luck on this issue and I have to place all html/js/css inside the ASP.NET project as you suggested. Oh well. – XDS Jun 27 '16 at 23:14