dotnet WebApi + Client - ClientApp SPA is not protected by .AddAuthentication or .AddOAuth
I have a standard dotnet WebApi + Client application with the ClientApp folder containing a create-react-app SPA
In my Startup - I have .AddAuthentication
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "MyChallenge"
})
.AddCookie()
.AddOAuth("MyChallenge", oauthAction);
and my oAuthAction
is
// https://auth0.com/docs/get-started/authentication-and-authorization-flow/add-login-auth-code-flow
Action<OAuthOptions> oauthAction = (OAuthOptions options) =>
{
var domainBase = "https://login.mydomain.com";
// When a user needs to sign in, they will be redirected to the authorize endpoint
options.AuthorizationEndpoint = $"{domainBase}/authorize";
// OAuth server is OpenID compliant, so request the standard openid
// scopes when redirecting to the authorization endpoint
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
// After the user signs in, an authorization code will be sent to a callback
// in this app. The OAuth middleware will intercept it
options.CallbackPath = new PathString("/auth/callback");
// The OAuth middleware will send the ClientId, ClientSecret, and the
// authorization code to the token endpoint, and get an access token in return
options.ClientId = "NONEOFYOURBUSINESS";
options.ClientSecret = "NICETRYBUTNOT";
options.TokenEndpoint = $"{domainBase}/token";
// Below we call the userinfo endpoint to get information about the user
options.UserInformationEndpoint = $"{domainBase}/userinfo";
// Describe how to map the user info we receive to user claims
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "given_name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get user info from the userinfo endpoint and use it to populate user claims
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<JsonElement>(responseContent);
context.RunClaimActions(user);
}
};
};
While every controller marked by [Authorize] is protected, my SPA static files are not. For "reasons" (that are not relevant) I need them to be.
I've looked around but I can't seem to find a way to protect the SPA files defined by
builder.Services.AddSpaStaticFiles(
configuration =>
{
configuration.RootPath = "ClientApp/build";
}
);
Or
app.UseSpa(
spa =>
{
spa.Options.SourcePath = "ClientApp";
if (app.Environment.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://127.0.0.1:3000");
}
}
);
to require the [Authorize] middleware - it seems like it should be simple - what am I missing? One would think this would be a simple / semi-common scenario.
Solution 1:
C# CODE
There is an OnPrepareResponse hook that you can use for this. The link provides a nice example.
WEB RESOURCES
Ir's pretty rare to secure static content, in either an SPA or a website, since most apps use at least one unauthenticated view, eg to present a login button, before the user authenticates. This in turn requires JS, CSS etc to be downloaded when there is no auth cookie yet.
I would potentially split static files into folders - those that need authorizing in one root folder, and those that don't in another. The files that need authorizing may need to run more stages of the ASP.Net pipeline, eg to process cookies.