Skip to content

Commit 84db9b2

Browse files
lenardt-gerhardtsLenardt Gerhardtslovasoa
authored
Added configuration option to skip OIDC authorization checks for certain endpoints (#969)
* Added posibility to bypass oidc authentication for certain endpoints * fixxed missing .clone() * Fixxed oidc_skip_endpoints not being optional * feat(oidc): Introduce protected path prefixes This commit replaces the OIDC endpoint blacklist with a path prefix whitelist. This is a more intuitive and secure approach for managing protected routes. The new `oidc_protected_paths` configuration option allows users to specify a list of URL prefixes that require OIDC authentication. By default, all paths are protected. The documentation has been updated to reflect this change, with clear examples and more user-friendly language. * docs(oidc): Improve OIDC documentation and examples This commit improves the OIDC documentation and the "single sign on" example to better demonstrate how to create a selective login system. The main documentation now includes a section on creating a public login page and the "single sign on" example has been updated to reflect this pattern. * Simplify OIDC middleware request handling for unprotected paths * docs(oidc): Improve single sign on example This commit improves the "single sign on" example to better demonstrate a public information page that adapts to the users login status and a separate protected page. * docs(oidc): Document oidc_protected_paths in configuration.md This commit updates the main configuration documentation to reflect the new `oidc_protected_paths` option. It removes the outdated `oidc_skip_endpoints` and provides a more detailed explanation of how to create a mix of public and private pages. * Improve SSO demo UX - Update docker compose command to use `--watch` flag - Add watch configuration for SQLPage development - Enhance login page with hero component and better styling - Simplify protected page welcome message - Fix OIDC middleware path check logic - Update protected paths in config to use `/protected` instead of `/protected.sql` * Skip OIDC auth for non-protected paths later in middleware We still want to be able to access authenticated user's info in non-authenticated parts of the app. We crucially need to check request.path() == SQLPAGE_REDIRECT_URI before the protected_paths check * Added whitelist option * Update configuration.md * Improve OIDC public paths documentation The documentation now provides clearer examples and explains the interaction between public and protected paths more precisely. Also removes the now-unused default_oidc_public_paths function since the field's default is handled by serde's default for Vec. * Add OidcConfig method to check public paths The new `is_public_path` method consolidates the logic for checking if a path should bypass OIDC authentication. This replaces the previous inline checks for public and protected paths. * fix default empty public paths * Update SSO example with new image path and public access rules - Change hero image path in login page - Remove protected.sql as it's no longer needed - Update sqlpage.yaml to allow public access to /protected/public --------- Co-authored-by: Lenardt Gerhardts <[email protected]> Co-authored-by: lovasoa <[email protected]>
1 parent 57ba206 commit 84db9b2

File tree

11 files changed

+145
-44
lines changed

11 files changed

+145
-44
lines changed

configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Here are the available configuration options and their default values:
2525
| `configuration_directory` | `./sqlpage/` | The directory where the `sqlpage.json` file is located. This is used to find the path to [`templates/`](https://sql-page.com/custom_components.sql), [`migrations/`](https://sql-page.com/your-first-sql-website/migrations.sql), and `on_connect.sql`. Obviously, this configuration parameter can be set only through environment variables, not through the `sqlpage.json` file itself in order to find the `sqlpage.json` file. Be careful not to use a path that is accessible from the public WEB_ROOT |
2626
| `allow_exec` | false | Allow usage of the `sqlpage.exec` function. Do this only if all users with write access to sqlpage query files and to the optional `sqlpage_files` table on the database are trusted. |
2727
| `max_uploaded_file_size` | 5242880 | Maximum size of forms and uploaded files in bytes. Defaults to 5 MiB. |
28+
| `oidc_protected_paths` | `["/"]` | A list of URL prefixes that should be protected by OIDC authentication. By default, all paths are protected (`["/"]`). If you want to make some pages public, you can restrict authentication to a sub-path, for instance `["/admin", "/users/settings"]`. |
29+
| `oidc_public_paths` | `[]` | A list of URL prefixes that should be publicly available. By default, no paths are publicly accessible (`[]`). If you want to make some pages public, you can bypass authentication for a sub-path, for instance `["/public/", "/assets/"]`. Keep in mind that without the closing backslashes, that any directory or file starting with `public` or `assets` will be publicly available. This will also overwrite any protected path restriction. If you have a private path `/private` and you define the public path `/private/public/` everything in `/private/public/` will be publicly accessible, while everything else in private will still need authentication.
2830
| `oidc_issuer_url` | | The base URL of the [OpenID Connect provider](#openid-connect-oidc-authentication). Required for enabling Single Sign-On. |
2931
| `oidc_client_id` | sqlpage | The ID that identifies your SQLPage application to the OIDC provider. You get this when registering your app with the provider. |
3032
| `oidc_client_secret` | | The secret key for your SQLPage application. Keep this confidential as it allows your app to authenticate with the OIDC provider. |
@@ -90,7 +92,7 @@ This allows you to keep the password separate from the connection string, which
9092

9193
### OpenID Connect (OIDC) Authentication
9294

93-
OpenID Connect (OIDC) is a secure way to let users log in to your SQLPage application using their existing accounts from popular services. When OIDC is configured, all access to your SQLPage application will require users to log in through the chosen provider. This enables Single Sign-On (SSO), allowing you to restrict access to your application without having to handle authentication yourself.
95+
OpenID Connect (OIDC) is a secure way to let users log in to your SQLPage application using their existing accounts from popular services. When OIDC is configured, you can control which parts of your application require authentication using the `oidc_protected_paths` option. By default, all pages are protected. You can specify a list of URL prefixes to protect specific areas, allowing you to have a mix of public and private pages.
9496

9597
To set up OIDC, you'll need to:
9698
1. Register your application with an OIDC provider

examples/official-site/sso/single_sign_on.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Setting Up Single Sign-On in SQLPage
22

3-
43
When you want to add user authentication to your SQLPage application, you have two main options:
54

65
1. The [authentication component](/component.sql?component=authentication):
@@ -60,9 +59,7 @@ Create or edit `sqlpage/sqlpage.json` to add the following configuration keys:
6059
When you restart your SQLPage instance, it should automatically contact
6160
the identity provider, find its login URL, and the public keys that will be used to check the validity of its identity tokens.
6261

63-
The next time an user loads page on your SQLPage website, they will be redirected to
64-
the provider's login page. Upon successful login, they will be redirected back to
65-
the page they were initially requesting on your website.
62+
By default, all pages on your website will now require users to log in.
6663

6764
## Access User Information in Your SQL
6865

@@ -89,6 +86,40 @@ values
8986
(sqlpage.path(), sqlpage.user_info('sub'));
9087
```
9188

89+
## Restricting authentication to a specific set of pages
90+
91+
Sometimes, you don't want to protect your entire website with a login, but only a specific section.
92+
You can achieve this by adding the `oidc_protected_paths` option to your `sqlpage.json` file.
93+
94+
This option takes a list of URL prefixes. If a user requests a page whose address starts with one of these prefixes, they will be required to log in.
95+
96+
**Example:** Protect only pages in the `/admin` folder.
97+
98+
```json
99+
{
100+
"oidc_issuer_url": "https://accounts.google.com",
101+
"oidc_client_id": "your-client-id",
102+
"oidc_client_secret": "your-client-secret",
103+
"host": "localhost:8080",
104+
"oidc_protected_paths": ["/admin"]
105+
}
106+
```
107+
108+
In this example, a user visiting `/admin/dashboard.sql` will be prompted to log in, while a user visiting `/index.sql` will not.
109+
110+
### Creating a public login page
111+
112+
A common pattern is to have a public home page with a "Login" button that redirects users to a protected area.
113+
114+
With the configuration above, you can create a public page `login.sql` that is not in a protected path. This page can contain a simple link to a protected resource, for instance `/admin/index.sql`:
115+
116+
```sql
117+
select 'list' as component, 'actions' as title;
118+
select 'Login' as title, '/admin' as link, 'login' as icon;
119+
```
120+
121+
When a non-authenticated user clicks this "Login" link, SQLPage will automatically redirect them to your identity provider's login page. After they successfully authenticate, they will be sent back to the page they originally requested (`/admin/index.sql`).
122+
92123
## Going to Production
93124

94125
When deploying to production:
@@ -127,4 +158,4 @@ When deploying to production:
127158
- Verify your OIDC provider's logs for authentication attempts
128159
- In production, confirm your domain name matches exactly in both the OIDC provider settings and `sqlpage.json`
129160
- If [using a reverse proxy](/your-first-sql-website/nginx.sql), ensure it's properly configured to handle the OIDC callback path.
130-
- If you have checked everything and you think the bug comes from SQLPage itself, [open an issue on our bug tracker](https://github.com/sqlpage/SQLPage/issues).
161+
- If you have checked everything and you think the bug comes from SQLPage itself, [open an issue on our bug tracker](https://github.com/sqlpage/SQLPage/issues).

examples/single sign on/README.md

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Depending on your use case, you can choose the appropriate protocol for your app
2020
To run the demo, you just need docker and docker-compose installed on your machine. Then, run the following commands:
2121

2222
```bash
23-
docker-compose up
23+
docker compose up --watch
2424
```
2525

2626
This will start a Keycloak server and a SQLPage server. You can access the SQLPage application at http://localhost:8080.
@@ -46,14 +46,12 @@ SQLPage has built-in support for OIDC authentication since v0.35.
4646
This project demonstrates how to use it with the free and open source [Keycloak](https://www.keycloak.org/) OIDC provider.
4747
You can easily replace Keycloak with another OIDC provider, such as Google, or your enterprise OIDC provider, by following the steps in the [Configuration](#configuration) section.
4848

49-
### Important Note About OIDC Protection
49+
### Public and Protected Pages
5050

51-
When using SQLPage's built-in OIDC support, the entire website is protected behind authentication. This means:
52-
- All pages require users to be logged in
53-
- There is no way to have public pages alongside protected pages
54-
- Users will be automatically redirected to the OIDC provider's login page when accessing any page
51+
By default, SQLPage's built-in OIDC support protects the entire website. However, you can configure it to have a mix of public and protected pages using the `oidc_protected_paths` option in your `sqlpage.json` file.
52+
53+
This allows you to create a public-facing area (like a homepage with a login button) and a separate protected area for authenticated users.
5554

56-
If you need to have a mix of public and protected pages, you should use the [authentication component](/component.sql?component=authentication) instead.
5755

5856
### Configuration
5957

@@ -65,7 +63,8 @@ you need to configure it in your `sqlpage.json` file:
6563
"oidc_issuer_url": "https://your-keycloak-server/auth/realms/your-realm",
6664
"oidc_client_id": "your-client-id",
6765
"oidc_client_secret": "your-client-secret",
68-
"host": "localhost:8080"
66+
"host": "localhost:8080",
67+
"oidc_protected_paths": ["/protected"]
6968
}
7069
```
7170

@@ -91,15 +90,11 @@ select 'text' as component, 'Welcome, ' || sqlpage.user_info('name') || '!' as c
9190

9291
The demo includes several SQL files that demonstrate different aspects of OIDC integration:
9392

94-
1. `index.sql`: Shows how to:
95-
- Display user information using `sqlpage.user_info('email')`
96-
- Show all available user information using `sqlpage.id_token()`
93+
1. `index.sql`: A public page that shows a welcome message and a login button. If the user is logged in, it displays their email and a link to the protected page.
9794

98-
2. `protected.sql`: Demonstrates a page that is accessible to authenticated users
95+
2. `protected.sql`: A page that is only accessible to authenticated users. It displays the user's information.
9996

100-
3. `logout.sql`: Shows how to:
101-
- Remove the authentication cookie
102-
- Redirect to the OIDC provider's logout endpoint
97+
3. `logout.sql`: Logs the user out by removing the authentication cookie and redirecting to the OIDC provider's logout page.
10398

10499
### Docker Setup
105100

@@ -120,4 +115,3 @@ The demo uses Docker Compose to set up both SQLPage and Keycloak. The configurat
120115
- [SQLPage OIDC Documentation](https://sql-page.com/sso)
121116
- [OpenID Connect](https://openid.net/connect/)
122117
- [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
123-

examples/single sign on/docker-compose.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
services:
1010
sqlpage:
1111
image: lovasoa/sqlpage:main # Use the latest development version of SQLPage
12+
build:
13+
context: ../..
1214
volumes:
1315
- .:/var/www
1416
- ./sqlpage:/etc/sqlpage
@@ -23,6 +25,10 @@ services:
2325
depends_on:
2426
keycloak:
2527
condition: service_healthy
28+
develop:
29+
watch:
30+
- action: restart
31+
path: ./sqlpage/
2632

2733
keycloak:
2834
build:

examples/single sign on/index.sql

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
set user_email = sqlpage.user_info('email');
1+
SELECT 'shell' as component, 'My public app' as title;
22

3-
select 'shell' as component, 'My secure app' as title,
4-
'logout' as menu_item;
3+
set email = sqlpage.user_info('email');
54

6-
select 'text' as component,
7-
'You''re in !' as title,
8-
'You are logged in as *`' || $user_email || '`*.
9-
You have access to the [protected page](protected.sql).
5+
-- For anonymous users
6+
SELECT 'hero' as component,
7+
'/protected' as link,
8+
'Log in' as link_text,
9+
'Welcome' as title,
10+
'You are currently browsing as a guest. Log in to access the protected page.' as description,
11+
'/protected/public/hello.jpeg' as image
12+
WHERE $email IS NULL;
1013

11-
![open door](/assets/welcome.jpeg)'
12-
as contents_md;
13-
14-
select 'list' as component;
15-
select key as title, value as description
16-
from json_each(sqlpage.id_token());
14+
-- For logged-in users
15+
SELECT 'text' as component,
16+
'Welcome back, ' || sqlpage.user_info('name') || '!' as title,
17+
'You are logged in as ' || sqlpage.user_info('email') || '. You can now access the [protected page](/protected) or [log out](/logout).' as contents_md
18+
WHERE $email IS NOT NULL;

examples/single sign on/protected.sql

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
set user_email = sqlpage.user_info('email');
2+
3+
select 'shell' as component, 'My secure app' as title,
4+
'logout' as menu_item;
5+
6+
select 'text' as component,
7+
'You''re in, '|| sqlpage.user_info('name') || ' !' as title,
8+
'You are logged in as *`' || $user_email || '`*.
9+
10+
You have access to this protected page.
11+
12+
![open door](/assets/welcome.jpeg)'
13+
as contents_md;
14+
15+
select 'list' as component;
16+
select key as title, value as description
17+
from json_each(sqlpage.user_info_token());
1010 KB
Loading
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
oidc_issuer_url: http://localhost:8181/realms/sqlpage_demo
22
oidc_client_id: sqlpage
33
oidc_client_secret: qiawfnYrYzsmoaOZT28rRjPPRamfvrYr # For a safer setup, use environment variables to store this
4+
oidc_protected_paths:
5+
- /protected # Makes the website root is publicly accessible, requiring authentication only for the /protected path
6+
oidc_public_paths:
7+
- /protected/public # Adds an exception for the /protected/public path, which is publicly accessible too

src/app_config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,28 @@ pub struct AppConfig {
201201
#[serde(default = "default_oidc_scopes")]
202202
pub oidc_scopes: String,
203203

204+
/// Defines a list of path prefixes that should be protected by OIDC authentication.
205+
/// By default, all paths are protected.
206+
/// If you specify a list of prefixes, only requests whose path starts with one of the prefixes will require authentication.
207+
/// For example, if you set this to `["/private"]`, then requests to `/private/some_page.sql` will require authentication,
208+
/// but requests to `/index.sql` will not.
209+
/// NOTE: `OIDC_PUBLIC_PATHS` takes precedence over `OIDC_PROTECTED_PATHS`.
210+
/// For example, if you have `["/private"]` on the `protected_paths` like before, but also `["/private/public"]` on the `public_paths`, then `/private` requires authentication, but `/private/public` requires not authentication.
211+
/// You cannot make a path inside a public path private again. So expanding the previous example, if you now add `/private/public/private_again`, then this path will still be accessible.
212+
#[serde(default = "default_oidc_protected_paths")]
213+
pub oidc_protected_paths: Vec<String>,
214+
215+
/// Defines path prefixes to exclude from OIDC authentication.
216+
/// By default, no paths are excluded.
217+
/// Paths matching these prefixes will not require authentication.
218+
/// For example, if set to `["/public"]`, requests to `/public/some_page.sql` will not require authentication,
219+
/// but requests to `/index.sql` will still require it.
220+
/// To make `/protected/public.sql` public while protecting its containing directory,
221+
/// set `oidc_public_paths` to `["/protected/public.sql"]` and `oidc_protected_paths` to `["/protected"]`.
222+
/// Be aware that any path starting with `/protected/public.sql` (e.g., `/protected/public.sql.backup`) will also become public.
223+
#[serde(default)]
224+
pub oidc_public_paths: Vec<String>,
225+
204226
/// A domain name to use for the HTTPS server. If this is set, the server will perform all the necessary
205227
/// steps to set up an HTTPS server automatically. All you need to do is point your domain name to the
206228
/// server's IP address.
@@ -546,6 +568,10 @@ fn default_oidc_scopes() -> String {
546568
"openid email profile".to_string()
547569
}
548570

571+
fn default_oidc_protected_paths() -> Vec<String> {
572+
vec!["/".to_string()]
573+
}
574+
549575
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, Eq, Default)]
550576
#[serde(rename_all = "lowercase")]
551577
pub enum DevOrProd {

0 commit comments

Comments
 (0)