I realize the question sounds rather long and complicated! I'll try my best to explain. I have a ASP.NET Core 5 application which is a BFF (backend for frontend) for my Angular application. You MUST log in using Azure AD before you can access the SPA. The result of that is saved in a cookie, so we have 2 auth schemes (Azure AD and cookie). Resources like images are accessible without being authenticated.
The problem is that I have a HomeController that has an Index() action which has an [AuthorizeForScopes] and [Authorize] attribute that check if the user is logged in and authorized the scopes. If not, the AuthorizeForScopes will bring them to the Microsoft Login Page. If they are logged in, the method does a redirect to /home where the SPA runs.
This setup has some issues:
- I want the SPA to run on
/- This can't be done because the
HomeController.Index()method runs there. - Using something like
return PhysicalFile(..)is not a great solution; during development that path is different because of the webpack proxy etc... and on production the SPA isn't loaded correctly for some reason.
- This can't be done because the
- If the user is not logged in, I want to show a server side rendered login screen which acts as the "introduction page" of the app. It shows a "Log in with MS" button which should then trigger the MS login.
- This doesn't work because the
[Authorize]and[AuthorizeForScopes]tags immediately bring the user to the MS login screen.
- This doesn't work because the
I can't really accomplish both nicely. Here is some code to explain why:
Homecontroller
[Route("/")]
public class HomeController : Controller
{
[HttpGet]
[Authorize]
[AuthorizeForScopes(ScopeKeySection = "Scopes")]
public IActionResult Index()
{
// We can't run the SPA on "/" because we need to intercept that here to require that the user is authorized before accessing the SPA.
return Redirect("/home");
}
}
important middleware
// This is done AFTER useStaticFiles(), useEndpoints() and useSpaStaticFiles()
// Redirect the user to authenticate if the user isnt at this moment
// All static frontend related files are served using the UseSpaStaticFiles middleware
// What's left is the index.html
app.Use(async (context, next) =>
{
if (context.User?.Identity?.IsAuthenticated != true)
{
await context.ChallengeAsync(WellKnownAuthenticationSchemes.OpenIdConnect);
}
else
{
await next();
}
});
Auth
// Some other auth config is left out; it's not important.
services.Configure<CookieAuthenticationOptions>("Cookie", opt =>
{
opt.Cookie.IsEssential = true;
opt.Cookie.Name = "auth";
opt.Cookie.SameSite = SameSiteMode.Lax; // Required due to cross origin nature of signin-oidc callback
opt.Cookie.SecurePolicy = Environment.IsDevelopment()
? CookieSecurePolicy.SameAsRequest
: CookieSecurePolicy.Always;
opt.Cookie.HttpOnly = true;
opt.SlidingExpiration = true;
opt.ExpireTimeSpan = TimeSpan.FromDays(90);
});
services.AddAuthentication("Cookie");
services
.AddMicrosoftIdentityWebAppAuthentication(Configuration,
cookieScheme: "Cookie")
.EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetValue<string>("Scopes").Split(" "))
.AddDistributedTokenCaches();
So, to summarize:
- I want to show a custom login screen if the user is not logged in/has not authorized scopes which would probably run on
/loginand then pressing the button on that page runs that[Authorize]and[AuthorizeForScopes]logic that is currently onIndex() - I want the SPA to run on
/and only be accessible if the user is logged in and has authorized the scopes.
I would very much appreciate the help!