initial oidc example
This commit is contained in:
@@ -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 && \
|
||||
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user