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

Add ability to force MFA on OIDC Logins #971

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

s3n-w6i
Copy link

@s3n-w6i s3n-w6i commented May 9, 2024

This PR implements the ability to force users logging in through OIDC to have Multi-factor authentication enabled. It does this by looking at the amr claim provided by the IDP. This claim must contain the mfa value. Make sure that the IDP returns the amr claim correctly, otherwise authentication will fail.

Two new env vars have been added:

  • GRIST_OIDC_SP_FORCE_MFA
    If set to "true", the user will be forced to have multi-factor authentication enabled.
  • GRIST_OIDC_SP_MFA_SETTINGS_URL
    This is needed when GRIST_OIDC_SP_FORCE_MFA is set to true. Enter the URL where the user will be able to configure Multi-factor authentication on their account. This will be shown in the UI if the user does not have MFA enabled.

How to test:

Using Keycloak, it's fairly hard to add the amr claim, but Zitadel returns it by default. To test, create a new account on their cloud offering, hook it up to Grist and set GRIST_OIDC_SP_FORCE_MFA to true.
Log in without MFA enabled and get an error page.

Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this enhancement!

For what it is worth, I am a contributor but not part of Grist Labs, my review is advisory and the merge has to be done by an employee. I proposed some comments with the hope they may be useful.

I will try using deploying OIDC + MFA to test your PR.

app/server/lib/OIDCConfig.ts Outdated Show resolved Hide resolved
Comment on lines +96 to +97
cssErrorText(t("Multi-factor-authentication is required for accessing this site, but it is not set up on your \
account. Please enable it and try again.")),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest avoiding the \ at the end of a string, it is not standard JS (or is it required for the translation key collection?).

Suggested change
cssErrorText(t("Multi-factor-authentication is required for accessing this site, but it is not set up on your \
account. Please enable it and try again.")),
cssErrorText(t("Multi-factor-authentication is required for accessing this site, but it is not set up on your " +
"account. Please enable it and try again.")),

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plus should work as well, you're right. I'll test it later this week.

"Multi-factor authentication required{{suffix}}": "Multi-Faktor-Authentifizierung nötig{{suffix}}",
"Multi-factor-authentication is required for accessing this site, but it is not set up on your account. Please enable it and try again.": "Für den Zugriff auf diese Website ist Multi-Faktor-Authentifizierung erforderlich. Diese ist nicht in Ihrem Konto eingerichtet. Bitte richten Sie sie ein und versuchen Sie es erneut.",
"Set up Multi-factor authentication": "Multi-Faktor-Authentifizierung einrichten",
"Try again": "Erneut versuchen"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the localization, but could you do them through Weblate instead, please:
https://github.com/gristlabs/grist-core/blob/main/documentation/translations.md

Co-authored-by: Florent <[email protected]>
Comment on lines +98 to +102
cssButtonWrap(bigPrimaryButtonLink(
t("Set up Multi-factor authentication"),
{href: getGristConfig().mfaSettingsUrl, target: '_blank'},
testId('error-setup-mfa')
)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of showing that property only when getGristConfig().mfaSettingsUrl is filled with some value? This way, GRIST_OIDC_SP_MFA_SETTINGS_URL would be optional even when GRIST_OIDC_SP_FORCE_MFA is set.

Suggested change
cssButtonWrap(bigPrimaryButtonLink(
t("Set up Multi-factor authentication"),
{href: getGristConfig().mfaSettingsUrl, target: '_blank'},
testId('error-setup-mfa')
)),
getGristConfig().mfaSettingsUrl ? cssButtonWrap(bigPrimaryButtonLink(
t("Set up Multi-factor authentication"),
{href: getGristConfig().mfaSettingsUrl, target: '_blank'},
testId('error-setup-mfa')
)) : null,

suffix: getPageTitleSuffix(getGristConfig())
});

const searchParams = new URL(location.href).searchParams;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking: you can otherwise use the URLSearchParams class:

Suggested change
const searchParams = new URL(location.href).searchParams;
const searchParams = new URLSearchParams(location.search);

}

res.redirect(`/login/error/mfa-not-enabled?next=${targetUrlRelative}`);
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In another PR, I made something that may be of some help :

I hope my PR will be merged soon and that you could benefit from the changes.

You could still create another error page specifically for MFA.

However, I do not take advantage of the next param, and I do not see yet how to pass it using _sendAppPage. Is it something we would like to have absolutely? I tend to think it does not probably bring much discomfort (at least compared to the error itself).

Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested your work, it works fine, thanks. Still some changes I suggest regarding your PR, with the hope that my suggestions are clear, relevant and may help.

* be determined by OIDC's amr claim: It must include "mfa". Make sure that the IDP returns the amr claim
* correctly, otherwise authentication will fail.
* env GRIST_OIDC_SP_MFA_SETTINGS_URL
* This is needed when GRIST_OIDC_SP_FORCE_MFA is set to true. Enter the URL where the user will be able to
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree with my suggestion above, don't forget to adapt the comment here :).

@fflorent
Copy link
Collaborator

@pr0gr8mm3r I have my PR #883 merged, your PR has some conflicts.

I would be glad to help, don't hesitate to ask if you need!

@fflorent
Copy link
Collaborator

@pr0gr8mm3r Any news on this work? If you need help, please don't hesitate.

@s3n-w6i
Copy link
Author

s3n-w6i commented Nov 11, 2024

Hey @fflorent, been quite busy recently, and I currently don't have time to look. Please excuse my late reply. If you want to keep the PRs-list clean, feel free to close it. I'll open another one once I've gotten time to work on it again.

@fflorent
Copy link
Collaborator

fflorent commented Nov 11, 2024

@pr0gr8mm3r No worry, thanks for your reply!

Do you want me to resume your work? (Keeping crediting you for the first commits)

I am OK with both options: waiting or resuming your work.

@s3n-w6i
Copy link
Author

s3n-w6i commented Nov 12, 2024

If you have time, that'd be great! Feel free to continue :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants