initial oidc example

This commit is contained in:
lovasoa
2024-04-26 01:11:51 +02:00
parent 1e47b970a4
commit 2abcb77627
10 changed files with 4036 additions and 0 deletions
+4
View File
@@ -21,9 +21,13 @@ RUN apt-get update && \
fi && \
rustup target add $(cat TARGET) && \
cargo init .
# Build dependencies (creates a layer that avoids recompiling dependencies on every build)
COPY Cargo.toml Cargo.lock ./
COPY .cargo ./.cargo
RUN cargo build --target $(cat TARGET) --profile superoptimized
# Build the project
COPY . .
RUN touch src/main.rs && \
cargo build --target $(cat TARGET) --profile superoptimized && \
+1
View File
@@ -203,6 +203,7 @@ An alternative for Mac OS users is to use [SQLPage's homebrew package](https://f
- [Bulk data import from CSV files](./examples/official-site/examples/handle_csv_upload.sql) : A simple form letting users import CSV files to fill a database table.
- [Advanced authentication example using PostgreSQL stored procedures](https://github.com/mnesarco/sqlpage_auth_example)
- [Complex web application in SQLite with user management, file uploads, plots, maps, tables, menus, ...](https://github.com/DSMejantel/Ecole_inclusive)
- [Single sign-on with OpenID Connect](./examples/single%20sign%20on%20with%20openid%20connect/): An example of how to implement OAuth and OpenID Connect (OIDC) authentication in SQLPage.
You can try all the examples online without installing anything on your computer using [SQLPage's online demo on replit](https://replit.com/@pimaj62145/SQLPage).
@@ -0,0 +1,62 @@
# SQLPage OIDC Implementation Demo
This project demonstrates how to implement OpenID Connect (OIDC) authentication in a SQLPage application.
OIDC is an authentication protocol that allows users to authenticate with a third-party identity provider and then access applications without having to log in again. This is useful for single sign-on (SSO) scenarios where users need to access multiple applications with a single set of credentials.
OIDC can be used to implement a "Login with Google" or "Login with Facebook" button in your application, since these providers support the OIDC protocol.
SQLPage currently doesn't have a native OIDC implementation, but you can implement OIDC authentication in your SQLPage yourself. This project provides a basic implementation of OIDC authentication in a SQLPage application, using [Keycloak](https://www.keycloak.org/) as the OIDC provider.
## Running the Demo
To run the demo, you just need docker and docker-compose installed on your machine. Then, run the following commands:
```bash
docker-compose up
```
This will start a Keycloak server and a SQLPage server. You can access the SQLPage application at http://localhost:8080.
The credentials for the demo are:
- **Username: `demo`**
- **Password: `demo`**
The credentials to the keycloak admin console accessible at http://localhost:8180 are `admin/admin`.
## Configuration
If you want to use this implementation in your own SQLPage application,
with a different OIDC provider, here are the steps you need to follow:
1. Create an OIDC application in your OIDC provider (e.g., Keycloak). You will need to provide the following information:
- Redirect URI: This is the URL of your SQLPage application, followed by `/oidc_redirect_handler.sql`. For example, `https://example.com/oidc_redirect_handler.sql`.
- Client ID: This is a unique identifier for your application. You will need to provide this value to your SQLPage application as an environment variable.
- Client type (`public` or `confidential`). For this implementation, you should use `confidential` (sometimes called `web application`, `server-side`, or `backend`).
- Client secret: This is a secret key that is used to authenticate your application with the OIDC provider. You will need to provide this value to your SQLPage application as an environment variable.
2. You need to replace the following placeholders in the `oidc_redirect_handler.sql` file with your actual values:
- `http://keycloak:8181/realms/sqlpage_demo/protocol/openid-connect/`: Replace this with the base URL of your OIDC implementation.
- `http://localhost:8080/`: Replace this with the URL of your application.
You also need to set the following environment variables:
- `OIDC_CLIENT_ID`: The client ID of your OIDC application.
- `OIDC_CLIENT_SECRET`: The client secret of your OIDC application.
## Overview
The main logic is contained in the `oidc_redirect_handler.sql` file. This script handles the OIDC redirect after the user has authenticated with the OIDC provider. It performs the following steps:
1. Checks if the `oauth_state` cookie matches the `state` parameter in the query string. This is a security measure to prevent CSRF attacks. If the states do not match, the user is redirected to the login page.
2. Exchanges the authorization code for an access token. This is done by making a POST request to the OIDC provider's token endpoint. The request includes the authorization code, the redirect URI, and the client ID and secret.
3. If the access token cannot be obtained, the user is redirected to the login page.
## References
- An accessible explanation of OIDC: https://annotate.dev/p/hello-world/learn-oauth-2-0-by-building-your-own-oauth-client-U2HaZNtvQojn4F
- [OpenID Connect](https://openid.net/connect/)
- [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
@@ -0,0 +1,38 @@
# This file lets you run the example with a single command: docker-compose up
# Download docker here: https://www.docker.com/products/docker-desktop
#
# This docker compose starts two services:
# 1. a SQLPage service that serves a simple page with a login button
# 2. a Keycloak service that acts as an OpenID Connect provider (manages users and authentication)
#
services:
sqlpage:
image: lovasoa/sqlpage:main # Use the latest development version of SQLPage
volumes:
- .:/var/www
- ./sqlpage:/etc/sqlpage
ports:
- 8080:8080
environment:
- OIDC_CLIENT_ID=sqlpage
- OIDC_CLIENT_SECRET=qiawfnYrYzsmoaOZT28rRjPPRamfvrYr
- RUST_LOG=sqlpage=debug
networks:
- sqlpage-network
keycloak:
image: keycloak/keycloak
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
ports:
- 8181:8181
volumes:
- ./keycloak-configuration.json:/opt/keycloak/data/import/realm.json
command: start-dev --import-realm --http-port 8181
networks:
- sqlpage-network
networks:
sqlpage-network:
@@ -0,0 +1,8 @@
select 'button' as component;
set $user_email = (select email from user_sessions where session_id = sqlpage.cookie('session_id'));
select 'Login' as title, '/oidc_login.sql' as link where $user_email is null;
select CONCAT('Currentlty logged in as ',$user_email,'. Log out ?') as title,
'/oidc_logout.sql' as link where $user_email is not null;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
set $oauth_state = sqlpage.random_string(32);
SELECT 'cookie' as component, 'oauth_state' as name, $oauth_state as value;
select 'redirect' as component,
'http://localhost:8181/realms/sqlpage_demo/protocol/openid-connect/auth' -- replace this with the URL of your OpenID Connect provider
|| '?response_type=code'
|| '&client_id=' || sqlpage.url_encode(sqlpage.environment_variable('OIDC_CLIENT_ID'))
|| '&redirect_uri=http://localhost:8080/oidc_redirect_handler.sql' -- replace this with the URL of your application
|| '&state=' || $oauth_state
|| '&scope=openid+profile+email'
|| '&nonce=' || sqlpage.random_string(32)
as link;
@@ -0,0 +1,10 @@
-- remove the session cookie
select 'cookie' as component, 'session_id' as name, true as remove;
-- remove the session from the database
delete from user_sessions
where session_id = sqlpage.cookie('session_id');
-- redirect the user to the oidc provider to logout
select 'redirect' as component,
'http://localhost:8181/realms/sqlpage_demo/protocol/openid-connect/logout' -- replace this with the logout URL of your OpenID Connect provider
|| '?redirect_url=http://localhost:8080/' -- replace this with the URL of your application
as link;
@@ -0,0 +1,50 @@
-- If the oauth_state cookie does not match the state parameter in the query string, then the request is invalid (CSRF attack)
-- and we should redirect the user to the login page.
select 'redirect' as component, '/oidc_login.sql' as link
where sqlpage.cookie('oauth_state') != $state;
-- Exchange the authorization code for an access token
set $authorization_code_request = json_object(
'url', 'http://keycloak:8181/realms/sqlpage_demo/protocol/openid-connect/token', -- replace this with the URL of your OpenID Connect provider
'method', 'POST',
'headers', json_object(
'Content-Type', 'application/x-www-form-urlencoded'
),
'body', 'grant_type=authorization_code'
|| '&code=' || $code
|| '&redirect_uri=http://localhost:8080/oidc_redirect_handler.sql' -- replace this with the URL of your application
|| '&client_id=' || sqlpage.environment_variable('OIDC_CLIENT_ID')
|| '&client_secret=' || sqlpage.environment_variable('OIDC_CLIENT_SECRET')
);
set $access_token = sqlpage.fetch($authorization_code_request);
-- Redirect the user to the login page if the access token could not be obtained
select 'redirect' as component, '/oidc_login.sql' as link
where $access_token is null or $access_token->>'error' is not null;
-- At this point we have $access_token which contains {"access_token":"eyJ...", "scope":"openid profile email" }
-- Fetch the user's profile
set $profile_request = json_object(
'url', 'http://keycloak:8181/realms/sqlpage_demo/protocol/openid-connect/userinfo', -- replace this with the URL of your OpenID Connect provider
'method', 'GET',
'headers', json_object(
'Authorization', 'Bearer ' || ($access_token->>'access_token')
)
);
set $user_profile = sqlpage.fetch($profile_request);
-- Redirect the user to the login page if the user's profile could not be obtained
select 'redirect' as component, '/oidc_login.sql' as link
where $user_profile is null or $user_profile->>'error' is not null;
-- at this point we have $user_profile which contains {"sub":"0cc01234","email_verified":false,"name":"John Smith","preferred_username":"demo","given_name":"John","family_name":"Smith","email":"demo@example.com"}
-- Now we have a valid access token, we can create a session for the user
-- in our database
insert into user_sessions(session_id, user_id, email)
values(sqlpage.random_string(32), $user_profile->>'sub', $user_profile->>'email') -- you can get additional information like 'name', 'given_name', 'family_name', 'email_verified', 'preferred_username', 'picture' from the user profile
returning 'cookie' as component, 'session_id' as name, session_id as value;
-- Redirect the user to the home page
select 'redirect' as component, '/' as link;
@@ -0,0 +1,7 @@
-- Table to store user sessions
CREATE TABLE user_sessions(
session_id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)