Discourse. SSO in 10 steps (Docker based)
How to setup SSO for Discourse when you have a Ruby on Rails web project as a Login server
Step 1. Stable Branch for the Win!
Always use stable branch to start your experiments with Discourse.
https://github.com/discourse/discourse/tree/stable
Main branch is unstable and it is normal when it does not work.
Step 2. Installation
- https://github.com/discourse/discourse/blob/main/docs/INSTALL.md
- https://meta.discourse.org/t/install-discourse-on-macos-for-development/15772
My docker-compose.yml may help
name: discourse
services:
postgres:
image: postgres:13.4
environment:
- POSTGRES_USER=postgres
- POSTGRES_DB=discourse_development
- POSTGRES_PASSWORD=qwerty
- PSQL_HISTFILE=/root/log/.psql_history
ports:
- ${POSTGRES_PORT-5432}:${POSTGRES_PORT:-5432}
volumes:
- ./data/PSQL:/var/lib/postgresql/data
redis:
image: redis:7.4.2
volumes:
- ./data/REDIS:/data
app:
build:
context: ..
dockerfile: ./docker/Dockerfile
environment:
- DISCOURSE_DB_HOST=postgres
- DISCOURSE_DB_PORT=5432
- DISCOURSE_DEV_DB=discourse_development
- DISCOURSE_DB_USERNAME=postgres
- DISCOURSE_DB_PASSWORD=qwerty
- DISCOURSE_REDIS_HOST=redis
- DISCOURSE_REDIS_PORT=6379
ports:
- "3000:3000"
- "4200:4200"
volumes:
- ..:/app
command: sleep infinity
My Dockerfile (Mac OS / M1)
# https://meta.discourse.org/t/install-discourse-on-ubuntu-or-debian-for-development/14727/1
# bash <(wget -qO- https://raw.githubusercontent.com/discourse/install-rails/main/linux)
# https://github.com/discourse/discourse/blob/main/docs/DEVELOPER-ADVANCED.md
FROM ruby:3.2-bookworm
ARG PROJECT_PATH=${PROJECT_PATH:-__PROJECTS__/discourse}
# INSTALL DEPENDENCIES
#
# build-essential - Essential build tools for compiling software
# libxslt1-dev - Library for XSLT processing
# libcurl4-openssl-dev - Library for HTTP requests with SSL support
# libksba8 - Libraries for cryptographic functionality
# libksba-dev - Libraries for cryptographic functionality
# libreadline-dev - Library for GNU readline (interactive command line)
# libssl-dev - SSL/TLS support library
# zlib1g-dev - Compression library
# libsnappy-dev - Snappy compression library
# libyaml-dev - YAML parsing library
# libpq-dev - PostgreSQL client library for database connections
# postgresql-client - PostgreSQL client for interacting with remote PostgreSQL databases
# telnet - Tool for testing network services
# nano - Command-line text editor
# git - Git version control system
# net-tools - Utilities for diagnosing and testing network connections
# dnsutils - Tools for DNS queries
# iputils-ping - Utilities for pinging hosts and testing connectivity
# curl - Curl command-line tool for transferring data with URLs
# wget - Wget command-line tool for downloading files
# tzdata - Timezone data for managing time zones
# advancecomp - Tools for optimizing PNG and MNG files
# jhead - Tool for displaying and manipulating Exif data
# jpegoptim - Tool for optimizing JPEG files
# libjpeg-turbo-progs - Tools for optimizing JPEG files
# optipng - Tool for optimizing PNG files
# pngcrush - Tool for optimizing PNG files
# pngquant - Tool for optimizing PNG files
# gnupg2 - GNU Privacy Guard for secure communication and data storage
# libjpeg-dev - Development files for the JPEG library
# libpng-dev - Development files for the PNG library
# libtiff-dev - Development files for the TIFF library
# libwebp-dev - Development files for the WebP library
# libxml2-dev - Development files for the XML library
# libltdl-dev - Development files for the libtool library
# libfreetype6-dev - Development files for the FreeType library
# liblcms2-dev - Development files for the Little CMS library
# liblqr-1-0-dev - Development files for the Liquid Rescale library
# libfftw3-dev - Development files for the Fastest Fourier Transform in the West library
# ghostscript - Interpreter for the PostScript language and PDF
#
RUN apt-get update -qq && apt-get install -y \
build-essential \
libxslt1-dev \
libcurl4-openssl-dev \
libksba8 \
libksba-dev \
libreadline-dev \
libssl-dev \
zlib1g-dev \
libsnappy-dev \
libyaml-dev \
libpq-dev \
postgresql-client \
telnet \
nano \
git \
net-tools \
dnsutils \
iputils-ping \
curl \
wget \
tzdata \
advancecomp \
jhead \
jpegoptim \
libjpeg-turbo-progs \
optipng \
pngcrush \
pngquant \
gnupg2 \
libjpeg-dev \
libpng-dev \
libtiff-dev \
libwebp-dev \
libxml2-dev \
libltdl-dev \
libfreetype6-dev \
liblcms2-dev \
liblqr-1-0-dev \
libfftw3-dev \
ghostscript \
&& rm -rf /var/lib/apt/lists/*
# WORKING DIRECTORY
#
WORKDIR /tmp
# Download and install ImageMagick 7
RUN cd /tmp && wget https://imagemagick.org/download/ImageMagick.tar.gz && \
tar xvzf ImageMagick.tar.gz && \
cd ImageMagick-* && \
./configure && \
make && \
make install && \
ldconfig /usr/local/lib && \
cd .. && \
rm -rf ImageMagick* && \
magick --version
# OXIPNG - OPTIMIZE PNG FILES (ARM64)
# https://github.com/shssoichiro/oxipng/releases/
RUN wget https://github.com/shssoichiro/oxipng/releases/download/v9.1.3/oxipng_9.1.3-1_arm64.deb
RUN dpkg -i oxipng_9.1.3-1_arm64.deb
SHELL ["/bin/bash", "-c"]
# INSTALL NODEJS AND PNPM AND YARN
#
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
RUN source /root/.nvm/nvm.sh && nvm install lts/hydrogen --default
# RUN source /root/.nvm/nvm.sh && \
# corepack enable pnpm && \
# corepack use pnpm@9
RUN source /root/.nvm/nvm.sh && \
npm install -g yarn
# WORKING DIRECTORY
#
WORKDIR /app
# INSTALL RUBY ON RAILS DEPENDENCIES
#
RUN gem install bundler -v 2.5.9
COPY ${PROJECT_PATH}/Gemfile /app/Gemfile
COPY ${PROJECT_PATH}/Gemfile.lock /app/Gemfile.lock
RUN bundle install --frozen
# INSTALL NODEJS DEPENDENCIES
#
RUN source /root/.nvm/nvm.sh && npm install -g svgo
COPY ${PROJECT_PATH}/package.json /app/package.json
COPY ${PROJECT_PATH}/patches /app/patches
RUN source /root/.nvm/nvm.sh && yarn -v
Step 4. DB setup
export SKIP_MULTISITE=1
congif/database.yml
development:
adapter: postgresql
database: discourse_development
username: postgres
password: password
host: postgres
port: 5432
pool: 5
test:
adapter: postgresql
database: discourse_test
username: postgres
password: password
host: postgres
port: 5432
pool: 5
$ yarn install
$ SKIP_MULTISITE=1 bundle exec rake db:create db:migrate
Step 5. Create Admin
In the container on localhost
$ bin/rails admin:create
Email: test@newforum.com
Password:
Repeat password:
Ensuring account is active!
Account created successfully with username `newforum`
Do you want to grant Admin privileges to this account? (Y/n) Y
Your account now has Admin privileges!
Step 6. Launch the project
export DISCOURSE_HOSTNAME=localhost
export DISCOURSE_PORT=4200
export SKIP_MULTISITE=1
bin/ember-cli -u
Step 7. DiscourseConnect (SSO) Settings
enable_discourse_connect
: must be enabled, global switchdiscourse_connect_url
: the offsite URL users will be sent to when attempting to log ondiscourse_connect_secret
: a secret string used to hash SSO payloads. Ensures payloads are authentic.
My Login server is launched on localhost:3002
enable discourse connect: true
discourse connect allowed redirect domains: http://localhost:3002
discourse connect url: http://localhost:3002/connect/sso
discourse connect secret: MySecretKey
Step 8. Login Button Now leads to http://localhost:3002/connect/sso
http://localhost:3002/connect/sso?sso=PAYLOAD&sig=SIG
class SsoController < ActionController::Base
def sso
render json: params
end
end
Rails.application.routes.draw do
get '/connect/sso', to: 'sso#sso', as: 'sso'
end
Let’s learn a request to Loging server
Step 9. SSO controller on your side
Let’s implement SSO controller
require 'base64'
require 'openssl'
class SsoController < ActionController::Base
# Must be equal to what you saved in Discourse settings
DISCOURSE_SECRET = "MySecretKey"
before_action :store_user_location!, if: :storable_location?
before_action :authenticate_user!
def sso
# Extract parameters from the request
sso = params[:sso]
sig = params[:sig]
# Validate the signature
unless valid_signature?(sso, sig)
render plain: 'Invalid signature', status: :unauthorized
return
end
# Decode the SSO payload
sso_params = decode_sso_payload(sso)
# Build a payload to send back to Discourse
response_payload = {
nonce: sso_params['nonce'],
email: current_user.email,
external_id: current_user.id.to_s,
username: current_user.username
}
# Generate response payload
encoded_response, response_sig = generate_response(sso_params, response_payload)
# http://postgres:4200/session/sso_login?sso=PAYLOAD=&sig=SINGNATURE
# Redirect back to Discourse
# MAY NEED FOR ADDITIONAL SETTINGS
redirect_to "#{sso_params['return_sso_url']}?sso=#{encoded_response}&sig=#{response_sig}"
end
private
# Validates the signature using the Discourse secret key
def valid_signature?(sso, sig)
computed_sig = OpenSSL::HMAC.hexdigest('sha256', DISCOURSE_SECRET, sso)
computed_sig == sig
end
# Decodes the Base64-encoded SSO payload
def decode_sso_payload(sso)
decoded_sso = Base64.decode64(sso)
Rack::Utils.parse_nested_query(decoded_sso)
end
# Generates the response payload and its signature
def generate_response(sso_params, response_payload)
raw_response = URI.encode_www_form(response_payload)
encoded_response = Base64.strict_encode64(raw_response)
response_sig = OpenSSL::HMAC.hexdigest('sha256', DISCOURSE_SECRET, encoded_response)
[encoded_response, response_sig]
end
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
session[:return_to] = request.original_url
end
end
SiteSetting.port = 4200
SiteSetting.force_hostname = “localhost”
Discourse.base_url => “http://localhost:4200"
Step 10. Login and see your new Account
Conclusion
Now you know how to setup SSO for Discourse if your Login server it a Rails based application.
Happy coding!
Follow me on github: https://github.com/the-teacher