'Web App SPA & OIDC: How to properly authenticate before accessing the front?

I have a regular web app, composed as usual of a frontend SPA (angular) and a backend.

The server calls are protected from unauthorized calls through authentication provided by OpenID Connect.

My web app is accessible from the internet, and though the front by itself does not contain any user data (which is saved in a database in the backend), I still want to prevent non-users from accessing my front code, because it would leak features that I don't want non-users to know about.

Similarly, I also want to prevent regular users from being able to see admin features (/admin part of my website).

I know the whole point of SPA is to download everything in a single page load and then make dynamic changes, but I still want users to only download code for which they have appropriate rights (for example, you can download the 3 tabs of the main menu in one go if you have access rights for these 3 tabs, but the admin page can only be downloaded if you have admin rights).

What options do I have to make sure the user is authenticated and authorized before downloading front code, and what are their advantages / drawbacks ?



Solution 1:[1]

HMTL

Usually in an SPA there is a single index.html file, as in this online SPA of mine. So when following a pure SPA architecture the HMTL reveals no information.

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>

        <base href='/spa/' />
        <title>OAuth Demo App</title>

        <link rel='stylesheet' href='bootstrap.min.css?t=1651226315563' integrity='sha256-YvdLHPgkqJ8DVUxjjnGVlMMJtNimJ6dYkowFFvp4kKs='>
        <link rel='stylesheet' href='app.css?t=1651226315563' integrity='sha256-B7pu+gcFspulW4zXfgczVtPcEuZ81tZRFYeRciEzWro='>
    </head>
    <body>
        <div id='root' class='container'></div>

        <script type='module' src='vendor.bundle.js?t=1651226315563' integrity='sha256-Rbqz3uAj5ST2WPw7vI0qTgvMYpLa5mzM85xgl96MRKI='></script>
        <script type='module' src='app.bundle.js?t=1651226315563' integrity='sha256-9vJJYO5Z/3lk5cbSuR+W0RW9QFA9ObGYLAXPBeL4pkY='></script>
    </body>
</html>

If multiple HTML files are returned, this is more of a website approach, and website stacks tend to protect HTML files using cookies to prevent any disclosure.

JAVASCRIPT

Javascript has traditionally not been secured in either SPAs or website tech stacks, since you need to allow Javascript to download in order to render an initial page, eg with a login button.

My deployed Javascript application code is not secured and is available in this bundle file. It is minified, so also does not reveal much. Of course, there should be nothing really secret in UI logic, and APIs should be making all security decisions for the SPA.

My SPA could use a package such as Javascript Obfuscator to further hide Javascript logic. The reason I have not done this is that exception stack trace lookup from source maps no longer works, but I would use obfuscation if I could find a solution where this was supported.

MULTIPLE JAVASCRIPT RESOURCES

If you need to secure Javascript you will need to build the SPA into multiple bundles such as these. Some website stacks provide this type of option, by hosting secured assets in a different folder. The web backend then checks that requests for those assets have an auth cookie:

- /unsecured/app-unauthenticated.bundle.js
- /secured/app-area1.bundle.js
- /secured/app-area2.bundle.js

You would need to look into code splitting and lazy loading. In your app you can control when dynamic inports occur, so your code would never request secured bundles when the user is unauthenticated.

AUTHORIZATION CHECKS

If you are concerned about a hacker accessing your JS bundles outside of your own code, as for my bundle above, then you could secure access to bundle files by checking for cookies, or even going so far as checking claims in underlying OAuth access tokens, though I would not do any of this.

My web host is the AWS Cloudfront Content Delivery Network (CDN), where Lambda Edge Extensions can run code before assets are served. Currently the only role of my lambda edge code is to serve a default document and issue recommended web security headers.

This code could be updated to make the same cookie checks as API gateway code, eg to return a 401 JSON response if it a valid cookie was not supplied. It would add considerable complexity though, and it is more standard to ensure at design time that UI code does not need securing.

SUMMARY

SPA security architecture is done best by separating web and API concerns. Personally I would put all my security efforts into securing APIs, and stick to obfuscation for Javascript.

Solution 2:[2]

You could use the CanLoad guard to check user permissions.

This guard will not only determine if users can navigate to a given route, it will also prevent the download of the module code when not authorized.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Julio Fuentes