Skip to content

Instantly share code, notes, and snippets.

@MGough
Last active August 30, 2022 12:03
Show Gist options
  • Save MGough/2100b56232fe4159ffcaadabfe4c38b8 to your computer and use it in GitHub Desktop.
Save MGough/2100b56232fe4159ffcaadabfe4c38b8 to your computer and use it in GitHub Desktop.
Discourse Connect SSO Auth Action - Based on: https://blog.leog.me/discourse-sso-with-auth0-e49486d0294a
// Based on: https://blog.leog.me/discourse-sso-with-auth0-e49486d0294a
// This must be the last action in the flow, as it potentially redirects the user to discourse and doesn't have a `onContinuePostLogin` hook as we're not expecting Discourse to return them
const DISCOURSE_AUTH0_CLIENT_ID = "<Your client ID>";
const DISCOURSE_DOMAIN = "<Your Discourse Domain>";
// We use this to avoid sending users to Discourse with unverified emails
// As we want Auth0 to handle email verification by default, not Discourse.
// If they're sent to discourse then they'll receive an email from discourse as well as Auth0 asking to independently verify their emails.
const DISCOURSE_NON_VERIFIED_EMAIL_REDIRECT_URL = "<A URL for users with unverified emails>";
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
// Check whether the Auth0 client is the one we want to apply this rule to
if (event.client.client_id === DISCOURSE_AUTH0_CLIENT_ID) {
// If they're verified it's safe to send them through to discourse
if (event.user.email_verified) {
// Check out Discourse's SSO implementation requirements already in discourse-sso package
// at https://meta.discourse.org/t/official-single-sign-on-for-discourse-sso/13045#heading--implement
const DiscourseSSO = require('discourse-sso');
// Setup sso_secret variable on your client variables on Auth0 so you don't need to have it inline in your code
const sso = new DiscourseSSO(event.secrets.sso_secret);
// Validate the query payload with its signature (it uses the sso_secret passed to the DiscourseSSO instance)
if (sso.validate(event.request.query.sso, event.request.query.sig)) {
// Extract nonce information
const nonce = sso.getNonce(event.request.query.sso);
const userparams = {
// Required, will throw exception otherwise
"nonce": nonce,
"external_id": event.user.user_id,
"email": event.user.email,
// Optional
"username": event.user.nickname,
// Just in case we somehow make a mistake one day...
// If we send a user that doesn't have a verified email then we tell Discourse to use its own email verification flow
"require_activation": !event.user.email_verified,
"suppress_welcome_message": false
};
const query = sso.buildLoginString(userparams);
api.redirect.sendUserTo(`https://${DISCOURSE_DOMAIN}/session/sso_login?${query}`);
return;
}
}
// They're trying to login to the forum, but their email isn't verified!
api.redirect.sendUserTo(DISCOURSE_NON_VERIFIED_EMAIL_REDIRECT_URL);
}
};
@leog
Copy link

leog commented Aug 29, 2022

Good job @MGough. NIT: the second parameter for api.redirect.sendUserTo accepts an object with the query, so it would be more readable like this:

var query = sso.buildLoginString(userparams);

api.redirect.sendUserTo(`${DISCOURSE_URL}/session/sso_login`, {
  query
});

@MGough
Copy link
Author

MGough commented Aug 29, 2022

Good job @MGough. NIT: the second parameter for api.redirect.sendUserTo accepts an object with the query, so it would be more readable like this:

var query = sso.buildLoginString(userparams);

api.redirect.sendUserTo(`${DISCOURSE_URL}/session/sso_login`, {
  query
});

Oh perfect, thank you for pointing that out. I should've spent slightly longer reading those docs... I'll get it updated when I have a moment.

@MGough
Copy link
Author

MGough commented Aug 30, 2022

Also worth noting some of the behaviour around redirects and email verification. I'm probably not going to update this gist to do more than the original, but I've rewritten my own action to redirect the user to our generic 'Please verify email' URL if their email if not verified for this specific Discourse application.

Subsequently I've updated our 'Verification Email' redirect URL to send the user directly to Discourse if they originally registered via this Discourse application. This avoids the case where we send two verification emails, one via Auth0 and one via Discourse

Edited: I've updated this to capture my email verification changes

I've updated for now not to use:

api.redirect.sendUserTo(`https://${DISCOURSE_DOMAIN}/session/sso_login?${query}`);

as I'm seeing issues with this, need to revisit the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment