Skip to content

Commit

Permalink
Added CSRF protection for WebsiteHost. #4
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Feb 10, 2024
1 parent bc82ef2 commit f7c642c
Show file tree
Hide file tree
Showing 73 changed files with 3,202 additions and 252 deletions.
61 changes: 61 additions & 0 deletions docs/decisions/0130-front-end-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Front-End Integration

* status: accepted
* date: 2024-02-10
* deciders: jezzsantos

# Context and Problem Statement

In SaaStack, we are demonstrating a template for a typical secure SaaS web product.

Most SaaS products today start out as only web applications (to achieve a single deployed version of a product). But later, there are always reasons to offer an API platform for integration into other products. Thus, SaaStack is demonstrating both from day one. In some SaaS businesses, other kinds of applications are also pursued, for example, Mobile apps, IoT devices, Desktop apps, integrations to marketplaces. In all these cases, a central Backend API is desirable at all stages of the business.

Backend APIs (typically REST APIs) are designed for two things:

1. To model complex business workflows (as opposed to what is more common to most developers, just modelling a relational database)
2. To be consumed by human developers building integrations to 3rd party systems, or other applications.

Frontend applications are designed for one thing:

1. To provide an optimized human or machine interface that interacts with a core product (i.e., a Backend API)

Every Frontend application has a discrete set of users (human or machine) and a discrete subset of use cases to expose.

Every Frontend application requires it to be optimized for usability on a specific platform. (viz: the difference between a web app and a mobile app).

No Backend API (collectively) can be optimal for each and every Frontend that integrates with it, but it can provide a superset of use cases for most Frontend applications that integrate with it.

Thus it becomes necessary for a more optimized translation between what data and performance can be provided by a Backend and what is required by any specific Frontend.

That translation can be done in one of 3 places:

1. In the centralized Backend, exact to each remote Frontend, defined by the remote Frontend on demand (i.e., multiple data and performance profiles in the same place, that change whenever a Frontend is added or changed)
2. In the remote FrontEnd.
3. In a dedicated "nearby" intermediary between the centralized Backend and remote Frontend. (i.e., may change whenever a specific Frontend changes)

> When it comes to the web and remote distributed systems, capacity, performance, and reliability are not guarantees. See the [Fallacies of Distributed Computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). Thus, any communication between components in a distributed system is going to compromise the performance and reliability of any remote system.
## Considered Options

The options are:

1. One centralized Backend, several "nearby" dedicated BEFFE + one remote Frontend
2. One centralized Backend, several remote Frontends (i.e., no intermediaries)
3. One remote Frontend, one Dedicated centralized Backend (i.e., no standalone API)

## Decision Outcome

`BEFFE`

- Every application type has its unique needs that a dedicated BEFFE can accommodate on-demand, without changing any Backend.
- A BEFFE is always deployed "nearby" to the Backend where they don't pay the network performance compromises that remote Frontends incur
- BEFFE can cache, aggregate, and filter data for a specific use-case in a Frontend, that would otherwise require too much data or latency if delivering from a Backend.
- A BEFFE can cache, aggregate and filter data from one or more Backend API or 3rd party systems to save on chatty communications with so many systems, and deliver just what the Frontend desires, how it desires it, in its most optimal form.
- Authentication/Authorization can be performed appropriately for each kind of Frontend by the BEFFE, rather than incurring that complexity in a Backend. Separation of concerns. For example, using secure cookies for untrusted Frontends (e.g. JavaScript apps), and tokens for trusted machine Backends.

## (Optional) More Information

See these strong arguments for distributed systems:

* https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends
* https://samnewman.io/patterns/architectural/bff/
21 changes: 13 additions & 8 deletions docs/design-principles/0090-authentication-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For Authorization, we are utilizing the minimal API "authorization policies" mec

In web clients, we will use (HTTPOnly) cookies to store JWT tokens (between the browser and the BEFFE), and will relay those JWTs to backend APIs via a reverse proxy.

We will prevent those JWT tokens ever being seen by any JavaScript running in a browser, and go to extra lengths to guard against CSRF attacks.
We will prevent those JWT tokens ever being seen by any JavaScript running in a browser, and go to extra lengths to guard against CSRF attacks.

![Credential Authentication](../images/Authentication-Credentials.png)

Expand Down Expand Up @@ -181,11 +181,6 @@ When the system is split into individual services each containing one or more su

API keys do not support refreshing issued API keys. When issuing the API key the client gets to define the expiry date, and should acquire a new API key before that expiry date themselves.

### Cookie Authorization

* TBD
* Performed by a BackendForFrontend (BEFFE) component, reverse-proxies the token hidden in the cookie, into a token passed to the backend

### Declarative Authorization Syntax

Authorization is both declarative (at the API layer), and enforced programmatically downstream in other layers.
Expand Down Expand Up @@ -237,8 +232,18 @@ Just like the roles above, there are two sets of "features" that apply separatel

All `End-Users` should have, at least, a minimum level of access to all untenanted API's based on a specific feature set, otherwise they literally have no access to do anything in the system. By default, every end-user in the system should have the `PlatformFeatures.Basic` feature set, used for accessing all untenanted APIs, and some Tenanted APIs, no matter what subscription plan they have.

In most SaaS products there are one or more pricing tiers. These are analog to "features".
In most SaaS products, there are one or more pricing tiers. These are analog to "features".

It is likely that every product will define its own custom tiers and features as a result.

By default, we've defined `Basic` to represent a free set of features, that every user should have at a bear minimum. This "feature set" needs to be made available even when the end-user loses their access to the rest of the system. For example, their free-trial expired. We've also defined `PaidTrial` to be used for a free-trial notion, and other tiers for paid pricing tiers. These are expected to be renamed for each product.
By default, we've defined `Basic` to represent a free set of features that every user should have at a bare minimum. This "feature set" needs to be made available even when the end-user loses their access to the rest of the system. For example, their free-trial expired. We've also defined `PaidTrial` to be used for a free-trial notion, and other tiers for paid pricing tiers. These are expected to be renamed for each product.

### Web Application Authentication/Authorization

Web applications are typically implemented in a browser using JavaScript. A browser application (or JS app) operates in a very hostile environment, where the running code is more open to attack than many other kinds of applications.

In this kind of environment, web browsers must collaborate with web applications to provide stateless and secure environments to run. Unlike other platforms, standard measures like encrypted storage of secrets are not 100% achievable, and stateless are harder to achieve.

There are few reliable measures that can be utilized, like Http-Only cookies, and even then, additional measures need to be taken (i.e., CSRF measures) to ensure that those are not compromised either.

See [BEFFE](0110-back-end-for-front-end.md) for more details on how Authentication and Authorization are implemented in the web application.
Loading

0 comments on commit f7c642c

Please sign in to comment.