Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically load the SSO IdP metadata at startup #279

Merged
merged 2 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ SMTP_PORT=1025

# SSO Config
HOSTNAME = http://localhost:8080
# Our apps's Entity ID, which is also the URL to our metadata.
# The SimpleSAML IDP's XML metadata
SAML_IDP_METADATA_URL=http://localhost:8081/simplesaml/saml2/idp/metadata.php
# Our apps's SP Entity ID, which is also the URL to our metadata.
SAML_ENTITY_ID=http://host.docker.internal:8080/sp
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ jobs:
if: steps.node_modules-cache-restore.outputs.cache-hit != 'true'
run: npm ci

- name: Run Login container
run: docker compose up -d login

- name: Get installed Playwright version
id: playwright-version
run: |
Expand Down
1 change: 1 addition & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The following configuration values must be set via environment variables.
| `SMTP_PORT` | The port to use for the SMTP server. Defaults to `587` in production (using `smtp.office365.com`) and `1025` in development (using ([MailHog](https:/mailhog/MailHog)) |
| `LETS_ENCRYPT_ACCOUNT_EMAIL` | The email address to use for the app's [single Let's Encrypt account](https://letsencrypt.org/docs/integration-guide/#one-account-or-many) |
| `REDIS_URL` | The Redis server to use for the worker queues. Defaults to `redis://redis:6379` in production and `localhost:6379` in development. |
| `SAML_IDP_METADATA_URL` | The URL of the SAML Identify Provider (IdP)'s metadata XML, which is downloaded at startup in order to configure authentication. |
| `SECRETS_OVERRIDE` | In development, to override the Docker secrets |

### Secrets
Expand Down
38 changes: 25 additions & 13 deletions app/saml.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@
// https:/remix-run/examples/pull/130/files/ec66b3060fac83eec2389eb0c96aad6d8ea4aed1#diff-02d2b71e481b2495b8a72af14f09fc28238298c7f1d19a540e37c9228985b0da
import * as samlify from 'samlify';
import * as validator from '@authenio/samlify-node-xmllint';
import secrets from './lib/secrets.server';

samlify.setSchemaValidator(validator);

const { SAML_IDP_METADATA } = secrets;
if (!SAML_IDP_METADATA) {
throw new Error('Missing SAML_IDP_METADATA secret');
const { SAML_IDP_METADATA_URL } = process.env;
if (!SAML_IDP_METADATA_URL) {
throw new Error('Missing SAML_IDP_METADATA_URL environment variable');
}
// Download the IDP's XML Metadata and use it to create our IdentityProvider
// NOTE: we purposely let this run in the background at startup, and then
// reuse the await'ed result whenever we need to use it below (only the first).
// If it fails for some reason, we'll crash the server (which is what we want
// since auth can't work without this metadata and the IdP being loaded).
const idp = fetch(SAML_IDP_METADATA_URL)
.then((res) => {
if (!res.ok) {
throw new Error(
`Unable to read SAML IdP metadata from SAML_IDP_METADATA_URL=${SAML_IDP_METADATA_URL}: status code=${res.status}`
);
}
return res.text();
})
.then((metadata) =>
samlify.IdentityProvider({
metadata,
})
);

// Here we configure the service provider: https://samlify.js.org/#/sp-configuration

const sp = samlify.ServiceProvider({
entityID: process.env.SAML_ENTITY_ID,
nameIDFormat: ['urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'],
Expand All @@ -31,29 +48,24 @@ const sp = samlify.ServiceProvider({
],
});

// Take the metadata stood up by the IDP and use it as the metadata for our IDP object
const idp = samlify.IdentityProvider({
metadata: SAML_IDP_METADATA,
});

export function metadata() {
return sp.getMetadata();
}

export async function createLoginRequest(redirectUrl: string = '/') {
const { context } = sp.createLoginRequest(idp, 'redirect');
const { context } = sp.createLoginRequest(await idp, 'redirect');
const url = new URL(context);
url.searchParams.append('RelayState', redirectUrl);
return url.href;
}

export async function createLogoutRequest(user: string) {
const { context } = sp.createLogoutRequest(idp, 'redirect', { nameID: user });
const { context } = sp.createLogoutRequest(await idp, 'redirect', { nameID: user });
return context;
}

export async function parseLoginResponse(body: { [k: string]: FormDataEntryValue }) {
const { extract } = await sp.parseLoginResponse(idp, 'post', {
const { extract } = await sp.parseLoginResponse(await idp, 'post', {
body,
});
const relayState = body.RelayState as string;
Expand Down
22 changes: 0 additions & 22 deletions dev-secrets/SAML_IDP_METADATA

This file was deleted.