Handle SSO over multiple subdomains

This commit is contained in:
Clement Michaud 2017-03-15 23:07:57 +01:00
parent e21f865631
commit 606ddc7308
10 changed files with 97 additions and 20 deletions

View File

@ -1,32 +1,41 @@
### Level of verbosity for logs # Level of verbosity for logs
logs_level: info logs_level: info
### Configuration of your LDAP # Configuration of LDAP
ldap: ldap:
url: ldap://ldap url: ldap://ldap
base_dn: ou=users,dc=example,dc=com base_dn: ou=users,dc=example,dc=com
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password
### Configuration of session cookies
# Configuration of session cookies
#
# _secret_ the secret to encrypt session cookies
# _expiration_ the time before cookies expire
# _domain_ the domain to protect.
# Note: the authenticator must also be in that domain. If empty, the cookie
# is restricted to the subdomain of the issuer.
session: session:
secret: unsecure_secret secret: unsecure_secret
expiration: 3600000 expiration: 3600000
domain: example.com
### The directory where the DB files will be saved
# The directory where the DB files will be saved
store_directory: /var/lib/auth-server/store store_directory: /var/lib/auth-server/store
### Notifications are sent to users when they require a password reset, a u2f # Notifications are sent to users when they require a password reset, a u2f
### registration or a TOTP registration. # registration or a TOTP registration.
### Use only one available configuration: filesystem, gmail # Use only one available configuration: filesystem, gmail
notifier: notifier:
### For testing purpose, notifications can be sent in a file # For testing purpose, notifications can be sent in a file
filesystem: filesystem:
filename: /var/lib/auth-server/notifications/notification.txt filename: /var/lib/auth-server/notifications/notification.txt
### Use your gmail account to send the notifications. You can use an app password. # Use your gmail account to send the notifications. You can use an app password.
# gmail: # gmail:
# username: user@example.com # username: user@example.com
# password: yourpassword # password: yourpassword

View File

@ -3,7 +3,8 @@
<title>Home page</title> <title>Home page</title>
</head> </head>
<body> <body>
You need to <a href="/authentication/login?redirect=/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/> You need to <a href="https://auth.test.local:8080/authentication/login?redirect=https://secret.test.local:8080/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/>
You can also log off by visiting the following <a href="/authentication/logout?redirect=/">link</a>. But you can also access it from another <a href="https://secret1.test.local:8080/secret.html">domain</a> or still <a href="https://secret2.test.local:8080/secret.html">another one</a>.<br/><br/>
You can also log off by visiting the following <a href="https://auth.test.local:8080/authentication/logout?redirect=https://secret.test.local:8080/">link</a>.
</body> </body>
</html> </html>

View File

@ -24,9 +24,7 @@ events {
http { http {
server { server {
listen 443 ssl; listen 443 ssl;
root /usr/share/nginx/html; server_name auth.test.local localhost;
server_name 127.0.0.1 localhost;
ssl on; ssl on;
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
@ -34,7 +32,7 @@ http {
error_page 401 = @error401; error_page 401 = @error401;
location @error401 { location @error401 {
return 302 https://localhost:8080/authentication/login?redirect=$request_uri; return 302 https://auth.test.local:8080/authentication/login?redirect=$scheme://$http_host$request_uri;
} }
location /authentication/ { location /authentication/ {
@ -56,6 +54,30 @@ http {
location /authentication/css/ { location /authentication/css/ {
proxy_pass http://auth/css/; proxy_pass http://auth/css/;
} }
}
server {
listen 443 ssl;
root /usr/share/nginx/html;
server_name secret1.test.local secret2.test.local secret.test.local localhost;
ssl on;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
error_page 401 = @error401;
location @error401 {
return 302 https://auth.test.local:8080/authentication/login?redirect=$scheme://$http_host$request_uri;
}
location /authentication/verify {
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://auth/authentication/verify;
}
location = /secret.html { location = /secret.html {
auth_request /authentication/verify; auth_request /authentication/verify;
@ -69,3 +91,4 @@ http {
} }
} }
} }

View File

@ -9,6 +9,7 @@ var u2f = require('authdog');
var nodemailer = require('nodemailer'); var nodemailer = require('nodemailer');
var nedb = require('nedb'); var nedb = require('nedb');
var YAML = require('yamljs'); var YAML = require('yamljs');
var session = require('express-session');
var config_path = process.argv[2]; var config_path = process.argv[2];
if(!config_path) { if(!config_path) {
@ -27,6 +28,7 @@ var config = {
ldap_users_dn: yaml_config.ldap.base_dn, ldap_users_dn: yaml_config.ldap.base_dn,
ldap_user: yaml_config.ldap.user, ldap_user: yaml_config.ldap.user,
ldap_password: yaml_config.ldap.password, ldap_password: yaml_config.ldap.password,
session_domain: yaml_config.session.domain,
session_secret: yaml_config.session.secret, session_secret: yaml_config.session.secret,
session_max_age: yaml_config.session.expiration || 3600000, // in ms session_max_age: yaml_config.session.expiration || 3600000, // in ms
store_directory: yaml_config.store_directory, store_directory: yaml_config.store_directory,
@ -48,5 +50,6 @@ deps.u2f = u2f;
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.ldap = ldap; deps.ldap = ldap;
deps.session = session;
server.run(config, ldap_client, deps); server.run(config, ldap_client, deps);

View File

@ -35,7 +35,7 @@ function sign_request(req, res) {
var u2f = req.app.get('u2f'); var u2f = req.app.get('u2f');
var meta = doc.meta; var meta = doc.meta;
var appid = u2f_common.extract_app_id(req); var appid = u2f_common.extract_app_id(req);
logger.info('U2F sign_request: Start authentication'); logger.info('U2F sign_request: Start authentication to app %s', appid);
return u2f.startAuthentication(appid, [meta]) return u2f.startAuthentication(appid, [meta])
}) })
.then(function(authRequest) { .then(function(authRequest) {

View File

@ -25,7 +25,7 @@ function register_request(req, res) {
var appid = u2f_common.extract_app_id(req); var appid = u2f_common.extract_app_id(req);
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers)); logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
logger.info('U2F register_request: Starting registration'); logger.info('U2F register_request: Starting registration of app %s', appid);
u2f.startRegistration(appid, []) u2f.startRegistration(appid, [])
.then(function(registrationRequest) { .then(function(registrationRequest) {
logger.info('U2F register_request: Sending back registration request'); logger.info('U2F register_request: Sending back registration request');

View File

@ -7,7 +7,6 @@ var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var speakeasy = require('speakeasy'); var speakeasy = require('speakeasy');
var path = require('path'); var path = require('path');
var session = require('express-session');
var winston = require('winston'); var winston = require('winston');
var UserDataStore = require('./user_data_store'); var UserDataStore = require('./user_data_store');
var Notifier = require('./notifier'); var Notifier = require('./notifier');
@ -28,13 +27,14 @@ function run(config, ldap_client, deps, fn) {
app.use(bodyParser.json()); app.use(bodyParser.json());
app.set('trust proxy', 1); // trust first proxy app.set('trust proxy', 1); // trust first proxy
app.use(session({ app.use(deps.session({
secret: config.session_secret, secret: config.session_secret,
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
secure: false, secure: false,
maxAge: config.session_max_age maxAge: config.session_max_age,
domain: config.session_domain
}, },
})); }));

View File

@ -8,6 +8,7 @@ var speakeasy = require('speakeasy');
var sinon = require('sinon'); var sinon = require('sinon');
var tmp = require('tmp'); var tmp = require('tmp');
var nedb = require('nedb'); var nedb = require('nedb');
var session = require('express-session');
var PORT = 8050; var PORT = 8050;
var BASE_URL = 'http://localhost:' + PORT; var BASE_URL = 'http://localhost:' + PORT;
@ -88,6 +89,7 @@ describe('test data persistence', function() {
deps.u2f = u2f; deps.u2f = u2f;
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.session = session;
var j1 = request.jar(); var j1 = request.jar();
var j2 = request.jar(); var j2 = request.jar();

View File

@ -7,6 +7,7 @@ var assert = require('assert');
var speakeasy = require('speakeasy'); var speakeasy = require('speakeasy');
var sinon = require('sinon'); var sinon = require('sinon');
var MockDate = require('mockdate'); var MockDate = require('mockdate');
var session = require('express-session');
var PORT = 8090; var PORT = 8090;
var BASE_URL = 'http://localhost:' + PORT; var BASE_URL = 'http://localhost:' + PORT;
@ -89,6 +90,7 @@ describe('test the server', function() {
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.ldap = ldap; deps.ldap = ldap;
deps.session = session;
_server = server.run(config, ldap_client, deps, function() { _server = server.run(config, ldap_client, deps, function() {
done(); done();

View File

@ -0,0 +1,37 @@
var sinon = require('sinon');
var server = require('../../src/lib/server');
var assert = require('assert');
describe('test server configuration', function() {
it('should set cookie scope to domain set in the config', function() {
var config = {};
config.session_domain = 'example.com';
config.notifier = {
gmail: {
user: 'user@example.com',
pass: 'password'
}
}
transporter = {};
transporter.sendMail = sinon.stub().yields();
var nodemailer = {};
nodemailer.createTransport = sinon.spy(function() {
return transporter;
  });
var deps = {};
deps.nedb = require('nedb');
deps.nodemailer = nodemailer;
deps.session = sinon.spy(function() {
return function(req, res, next) { next(); };
});
server.run(config, undefined, deps);
assert(deps.session.calledOnce);
assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com');
});
});