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

Better support for self-hosting login solution #44

Open
paulfitz opened this issue Jul 19, 2021 · 41 comments
Open

Better support for self-hosting login solution #44

paulfitz opened this issue Jul 19, 2021 · 41 comments
Labels
self-hosting Self-hosting setup that needs some love wishlist

Comments

@paulfitz
Copy link
Member

Self-hosted Grist would benefit from a well-supported login solution.

@paulfitz paulfitz created this issue from a note in Roadmap for Grist (Researching) Jul 19, 2021
@almereyda
Copy link

A common way of implementing authentication, without having to provide local authentication (similar to what @outline does), is to make use of the Passport.js ecosystem, providing some OIDC adapters by default, with the option the extend these with other authentication adaptor plugins.

@paulfitz paulfitz moved this from Researching to We're Working On It in Roadmap for Grist Aug 12, 2021
@paulfitz
Copy link
Member Author

Seems reasonable @almereyda. We have SAML support on the way, for people wanting to use Grist with their SSO.

@paulfitz
Copy link
Member Author

SAML support is now available. We'll need to do a write-up on how to configure it, but there is documentation at https://github.com/gristlabs/grist-core/blob/main/app/server/lib/SamlConfig.ts

@paulfitz
Copy link
Member Author

Authentik looks like a decent self-hosted sso solution. I just tested it and it works well with Grist, using SAML.

@FVilli
Copy link

FVilli commented Jan 22, 2022

I'm trying to setup grist self-hosted with authentik (docker).
//docker-compose
environment:
- DEBUG=1
- GRIST_SAML_IDP_SKIP_SLO
- GRIST_SAML_SP_HOST=https://grist.#my-domain#.cloud
- GRIST_SAML_IDP_UNENCRYPTED=1
- GRIST_SAML_IDP_LOGIN=https://auth.#my-domain#.cloud
- GRIST_SAML_IDP_LOGOUT=https://auth.#my-domain#.cloud
- GRIST_SAML_IDP_CERTS=/certificates/authentikSelf-signedCertificate_certificate.pem
- GRIST_SAML_SP_KEY=/certificates/GristAppCertificate_certificate.pem
- GRIST_SAML_SP_CERT=/certificates/GristAppCertificate_private_key.pem
...
all behind traefik reverse-proxy

In the browser console log:

Mixed Content: The page at 'https://grist.#my-domain#.cloud/o/docs/' was loaded over HTTPS, but requested an insecure resource 'http://grist.#my-domain#.cloud:8484/o/docs/api/session/access/active'. This request has been blocked; the content must be served over HTTPS.

So the domain appears to be correct but http and port aren't ....
Can I solve this issue with some configuration (env) ?

@paulfitz
Copy link
Member Author

Hi @FVilli, try setting the variables mentioned in #117 (comment):

One thing you could try is to set environment variables APP_DOC_URL and APP_HOME_URL to whatever way your site will be accessed by the user. If your site is now at https://example.com, you would set those two variables to that (including the https).

@anaisconce anaisconce moved this from We're Working On It to Researching in Roadmap for Grist Feb 2, 2022
@anaisconce anaisconce moved this from Researching to We're Working On It in Roadmap for Grist Feb 2, 2022
@anaisconce anaisconce changed the title Better support for self-hosting Better support for self-hosting login solution Feb 2, 2022
@anaisconce anaisconce moved this from We're Working On It to Researching in Roadmap for Grist Feb 2, 2022
@Gatherix
Copy link

We use LDAP and OIDC internally, would be helpful not having to setup a SAML provider

@jyio
Copy link
Contributor

jyio commented Feb 18, 2022

Thank you for sharing this interesting app! It already beats NocoDB and Baserow for me because it supports nested formulas. Good stuff.

I just got it working with Keycloak 17. It's a complicated beast that supports multiple multiple realms and protocols. Some things were not obvious to me (as a noob to Grist, Keycloak, and SAML)...

I already had a realm and user, so I only needed to create a Grist-specific SAML client:

  • The Client ID in Keycloak should be https://<grist-host>/saml/metadata.xml
  • Keycloak needs to know where to redirect after login/logout
    • Allow redirecting after login by setting the Valid Redirect URIs to https://<grist-host>/*
    • Enable redirecting after logout by setting the Logout Service Redirect Binding URL (under Fine Grain SAML Endpoint Configuration)
  • Protocol mappers are needed; the builtin mappers work, but their SAML attribute names should be changed to work with SAML2-js:
    • givenName: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
    • surname: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
    • email: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress

Grist needs the following information from Keycloak:

  • The SAML login/logout URL in Keycloak 17 is https://<keycloak-host>/realms/<realm>/protocol/saml (in Keycloak 16, it would've been https://<keycloak-host>/auth/realms/<realm>/protocol/saml; note the /auth)
  • The client's private key and certificate could be obtained from the Installation tab (on the client page)
  • Keycloak's server (realm) certificate could be obtained from Realm Settings in the General tab behind SAML 2.0 Identity Provider Metadata
  • These keys and certificates should be placed in files accessible to Grist (as indicated in SamlConfig.ts) with appropriate PEM headers/footers:
    • -----BEGIN RSA PRIVATE KEY-----/-----END RSA PRIVATE KEY-----
    • -----BEGIN CERTIFICATE-----/-----END CERTIFICATE-----

Here's how I start Grist (behind a reverse-proxy):

URL="https://<grist-host>"
SAML="https://<keycloak-host>/realms/<realm>/protocol/saml"
docker run -d --name=grist \
  -e GRIST_SINGLE_ORG=docs \
  -e APP_HOME_URL="${URL}" \
  -e APP_DOC_URL="${URL}" \
  -e GRIST_SAML_SP_HOST="${URL}" \
  -e GRIST_SAML_SP_KEY=/persist/saml/client.key \
  -e GRIST_SAML_SP_CERT=/persist/saml/client.crt \
  -e GRIST_SAML_IDP_LOGIN="${SAML}" \
  -e GRIST_SAML_IDP_LOGOUT="${SAML}" \
  -e GRIST_SAML_IDP_CERTS=/persist/saml/idp.crt \
  -e GRIST_SAML_IDP_UNENCRYPTED=1 \
  -v grist-data:/persist \
  -p 8484:8484 \
  gristlabs/grist

Oh... and if you, like me, are tempted to upgrade Keycloak from 16 to 17... it's a tarp! There are significant changes, so look before you leap.

I hope this helps someone. And yes, OIDC would be nice too, but I totally understand that you're scrambling to fix bugs after posting this on Reddit.

@paulfitz
Copy link
Member Author

Thanks a lot for posting that @jyio!

@apollo13
Copy link

It would be nice if something simple like Authelia would be supported. That would mean reading headers like Remote-User, Remote-Groups, Remote-Name, Remote-Email from the request (preferably configurable). This would enable a wider range of integration with existing login solutions and would allow people to try grist easier. I imagine that it would be relatively little code to add and probably also result in more people to be able to try grist (and maybe less support requests because SAML is a beast).

For instance I wanted to see how far I can take self-hosted currently, but I haven't been able to figure out whether teams are supported locally or one can just share with 2 members. I guess I will manage to setup SAML at some point to check that out :)

@helmut72
Copy link

Header-based authentication would be great. Then everyone can use the preferred reverse proxy. For me it's Caddy with caddy-security, for apollo13 it's Authelia. Most of the self-hosted admins use a reverse proxy.

@helmut72
Copy link

helmut72 commented Feb 19, 2022

@jyio can you post your complete Grist and Keycloak configuration please? I don't get a login page in Keycloak. Never used Keycloak for SAML, only some trials with OAuth (which worked). Thank you!

Edit:
Finally it works also for me with Keycloak. Also for Grist I was unsure about the login url for saml. Here is my saml config for Grist:

GRIST_SAML_SP_HOST=https://grist.example.com
GRIST_SAML_SP_KEY=/saml/sp.key
GRIST_SAML_SP_CERT=/saml/sp.crt

GRIST_SAML_IDP_LOGIN=https://keycloak.example.com/auth/realms/<my grist realm>/protocol/saml
GRIST_SAML_IDP_LOGOUT=https://keycloak.example.com/auth/realms/<my grist realm>/protocol/saml # doesn't work
GRIST_SAML_IDP_UNENCRYPTED=1
GRIST_SAML_IDP_CERTS=/saml/idp.crt

In Keycloak I also needed Valid Redirect URIs: https://grist.example.com/*. Rest of the Keycloak configuration is default after creating a new realm and adding one test user plus the informations from jyio.

@jyio
Copy link
Contributor

jyio commented Feb 19, 2022

Glad you got it working, @helmut72. Thank you for sharing your config! I just added my Grist config above.

I'm relatively new to Keycloak also. Because I recently installed 17.0.0, my login and logout URLs were both https://<keycloak-host>/realms/public/protocol/saml (note: no /auth, which was in 16..).

The logout does work (you get logged out from Grist and Keycloak) but Keycloak doesn't know where to redirect you after logging out. I set Logout Service Redirect Binding URL to https://<grist-host>/.

Protip: If you set Root URL to https://<grist-host>, then Valid Redirect URIs could be just /* and Logout Service Redirect Binding URL could be /.

@helmut72
Copy link

Thank you, @jyio. Now logout also works. Great experience this weekend, one is Grist, second is learning basics about SAML. Now need to dive deeper into both.

@dumblob
Copy link

dumblob commented Feb 21, 2022

Or maybe not overcomplicate things and just "integrate" with bitwarden (client is open source; server not but there is a very good open source reimplementation https://github.com/dani-garcia/vaultwarden/ ) and other (less secure but also widely used) credentials vaults.

Of course it misses some goodies (w3c/webappsec-change-password-url#34 , dani-garcia/vaultwarden#1691 ) but even then it proves to be the top approach to login management.

@helmut72
Copy link

Bitwarden/Vaultwarden isn't an authentication product, but a password manager. Can't be used for this part.

@dumblob
Copy link

dumblob commented Feb 21, 2022

@helmut72 that was the point. Use some whatever internal infrastructure for login & roles management but expose only the minimum - i.e. login + password. Let everything else up to the password manager.

@jyio
Copy link
Contributor

jyio commented Feb 21, 2022

Are you suggesting to use vaultwarden as some sort of centralized identity store, so Grist would just collect the credentials (from a login page) and verify them against vaultwarden? That's an interesting way to use vaultwarden... do you have any references for that?

Alternatively, just integrate with LDAP or AD, which are battle-tested for this case, but admittedly "enterprisey" (if you thought SAML was complicated). Or implement OIDC/OAuth2, which opens the door to an entire world of external identity providers (such as GitHub, Twitter, or Facebook). Or -- stay with me here -- configure Keycloak to consult an LDAP/AD server or delegate to an external SAML/OIDC provider, so you outsource the login as well as the identity.

@helmut72
Copy link

@dumblob A password manager is no user management tool. With a password manager you save login informations for your self hosted services and other services like Github or your outlook.com account or whatever, but you don't have created this user accounts in Bit/Vaultwarden.

@dumblob
Copy link

dumblob commented Feb 23, 2022

@dumblob A password manager is no user management tool. With a password manager you save login informations for your self hosted services and other services like Github or your outlook.com account or whatever, but you don't have created this user accounts in Bit/Vaultwarden.

This was my point because Bitwarden actually does support identity management (incl. hierarchies, permissions, etc.).

This whole idea comes down to the fact, that a client anyway needs some password manager in practice. So why to run/maintain/interface_with SAML/... server (which is really complicated) if clients anyway need* a password manager with identity support (like the Bitwarden client). So in practice you either need to synchronize the identity hierarchy + permissions

  1. between SAML server and password manager clients
  2. or between Grist and password manager clients

The difference being you already have Grist installed & configured (password manager server like Vaultwarden doesn't need to be installed & maintained by you - it's not a requirement contrary to the SAML/... solution).

An ideal solution would be if password manager servers would support SAML, OAuth, etc. But we're not yet there.

Don't take me wrong - I totally understand Grist utterly needs an enterprise-level architecture (sooner or later). So SAML & OAuth is definitely the way to go. I've just mentioned "password managers" as an alternative for those not wanting to have the hassle with Keycloak etc.


* yes, it'd have the nice side effect of pushing Grist users to use password managers as that's generally the safest known solution to credentials management

@almereyda
Copy link

almereyda commented Feb 23, 2022

After watching the conversation for a while, a similar discussion in another projects comes to my mind. Next to the creative attempts in rethinking use cases for password managers, derived from that other train of thought, I would second the notion that standards-compliant authentication protocals, such as SAML and OIDC, are useful for enabling generic usage of grist in standardised environments.

In the issue over at https://github.com/outline/outline/issues/1881 people have shown to provide very lean and minimal Identity and Access Management services soley for the purpose of emulating "local authentication". They are:

OIDC will prove to be a useful addition to grist, then.

@paulfitz
Copy link
Member Author

paulfitz commented Apr 7, 2022

I posted a template for self-hosting Grist using traefik-forward-auth. Looks like it has OIDC support. Could be worth trying hooking it up to one of @almereyda's suggestions.

@paulfitz
Copy link
Member Author

paulfitz commented Aug 17, 2022

Update: Grist and Dex (https://dexidp.io/) work particularly well together and allow a variety of login solutions to be configured pretty easily. I'm making a demo/template at https://github.com/gristlabs/grist-omnibus, and would be interested in feedback.

@helmut72
Copy link

Will this also use forward auth? This means that public sharing isn't possible?

@paulfitz
Copy link
Member Author

@helmut72 it uses forward auth internally, on particular login/logout related endpoints. Auth on other pages is via a cookie. So public sharing should be possible. If the forwarding were coming from an external source that is handling other web applications, or offers an independent way to login/logout, then there will be the problem of inconsistencies you hit in #207, which can be fixed but at the cost of public sharing becoming awkward. A special prefix for public sharing will be important to square that circle.

@helmut72
Copy link

it uses forward auth internally, on particular login/logout related endpoints.

Shouldn't it then also works with Caddy and Authelia? I've looked into our Omnibus:
https://github.com/paulfitz/grist-omnibus/blob/main/traefik.yaml

You only catch /auth/login and /_oauth in Traefik to use Dex. When I do this with Caddy and Authelia and logout in Grist, user {http.reverse_proxy.header.Remote-Email} is logged in Grist. Shouldn't it work if Grist notice that Remote-Email is empty and do whatever is needed to recognize that there isn't a signed-in user anymore?

@paulfitz
Copy link
Member Author

Shouldn't it then also works with Caddy and Authelia? I've looked into our Omnibus: https://github.com/paulfitz/grist-omnibus/blob/main/traefik.yaml
You only catch /auth/login and /_oauth in Traefik to use Dex. When I do this with Caddy and Authelia and logout in Grist, user {http.reverse_proxy.header.Remote-Email} is logged in Grist. Shouldn't it work if Grist notice that Remote-Email is empty and do whatever is needed to recognize that there isn't a signed-in user anymore?

@helmut72 something sounds off there. If I'm understanding correctly, when you have Grist with Caddy and Authelia, logging out using the "Sign Out" option in Grist is not working, it is leaving you logged in? There is a GRIST_FORWARD_AUTH_LOGOUT_PATH variable that is important here. For the omnibus it gets set to _oauth/logout, so that Grist will call traefik-forward-auth to actually sign out. And then traefik-forward-auth takes LOGOUT_REDIRECT=.../signed-out to come back to Grist afterwards.

@helmut72
Copy link

@paulfitz Thanks, interesting how it works with Traefik and Dex.

But my problem: The user called {http.reverse_proxy.header.Remote-Email} is logged in without hitting any login or logout. I only open grist.example.com.

I really wish Grist just supports OIDC next to SAML or header auth with an own path for public shared links.

@viniciusao
Copy link

Authentik looks like a decent self-hosted sso solution. I just tested it and it works well with Grist, using SAML.

@paulfitz I tried authentik and grist, it does indeed work, but when I sign out from grist I'm redirected to authentik login page and then is not possible to logging into grist again.

@paulfitz
Copy link
Member Author

Hi @viniciusao, are your settings along the lines suggested in https://support.getgrist.com/install/saml/#example-authentik ? Particularly GRIST_SAML_IDP_LOGIN and GRIST_SAML_IDP_LOGOUT?

@viniciusao
Copy link

viniciusao commented Dec 27, 2022

Hi @viniciusao, are your settings along the lines suggested in https://support.getgrist.com/install/saml/#example-authentik ? Particularly GRIST_SAML_IDP_LOGIN and GRIST_SAML_IDP_LOGOUT?

yes, I sign out grist, then sign out authentik and did the same login process but authentik returns "Whoops!
Something went wrong! Please try again later." after clicking on "redirecting to grist".

@viniciusao
Copy link

Hi @viniciusao, are your settings along the lines suggested in https://support.getgrist.com/install/saml/#example-authentik ? Particularly GRIST_SAML_IDP_LOGIN and GRIST_SAML_IDP_LOGOUT?

Turns out it was the SAML provider authorization flow type, I've set to "explicit consent", now I changed to "implicit".

@Chluz
Copy link

Chluz commented Apr 7, 2023

Hi, sorry for bringing the keycloak config question, as detailed by @jyio, back up a year later. I've configured Grist and Keycloak as listed above, but even with the correct mappers in place in Keycloak, my username is being picked up as the user identifier instead of the email. This is despite setting the userID setting in the Keycloak client to email.
This then means I have to set GRIST_DEFAULT_EMAIL to my username (rather than my email) to be identified as the original team owner.
Could someone share their Keycloak mappings to make sure I got them right before digging deeper ? Thanks in advance,

@helmut72
Copy link

@Chluz The mappers in this post works for me: #44 (comment)

Bildschirmfoto 2023-04-10 um 21 53 46

Details:
Bildschirmfoto 2023-04-10 um 21 56 01

@Chluz
Copy link

Chluz commented Apr 10, 2023

@Chluz The mappers in this post works for me: #44 (comment)

Bildschirmfoto 2023-04-10 um 21 53 46

Details: Bildschirmfoto 2023-04-10 um 21 56 01

Thank you, the snapshot showing the SAML Attribute name as a URI was what needed - I had previoulsy tried to use the names from #44 (comment) , not knowing URIs also could be used.

For those looking to replicate this, the easiest is to go to your "client scope" tab in keycloak, click the dedicated scope for that client, add mapper, predefined mapper, and add the 3 X500 example you have. Then edit each one to replace the SAML attribute name field with the appropriate URI from #44 (comment)

Thanks again !

@nycanit
Copy link

nycanit commented May 10, 2023

This month Google posted this blog about their support for the Passkey standard alongside Apple and Microsoft:
https://blog.google/technology/safety-security/the-beginning-of-the-end-of-the-password/
"And today, ahead of World Password Day, we’ve begun rolling out support for passkeys across Google Accounts on all major platforms. They’ll be an additional option that people can use to sign in, alongside passwords, 2-Step Verification (2SV), etc."

Passkey gives the option of signup and signin using Apple FaceID, Android fingerprint or Windows Hello (Face, fingerprint etc). Currently two webauthn libraries support Passkey - webauthn_rs in rust and simplewebauthn in typescript:
https://passkeys.dev/docs/tools-libraries/libraries/

Here is a demo put up by webauthn_rs that let me both sign up and sign in using fingerprint on macbook:
https://webauthn.firstyear.id.au/

For notebooks that don't have a fingerprint sensor you can use an Iphone FaceID or Android fingerprint sensor. The demo also supports secure Fido keys from Yubico and others:
https://www.amazon.com/Yubico-YubiKey-Factor-Authentication-Security/dp/B07HBCTYP1

And then have passwords and oauth fallback for those who prefer not to get too fancy. Biometrics would be a nice way to log into grist.

@helmut72
Copy link

helmut72 commented May 11, 2023

And then have passwords and oauth fallback for those who prefer not to get too fancy. Biometrics would be a nice way to log into grist.

This is supported for years with the IDP Keycloak. You don't enable webauthn on apps that use modern authentication like Grist, but on the IDP.

@raph
Copy link

raph commented Sep 8, 2023

@nycanit zitadel support webauthn / fido2 passkey standard and also SAML, i'm looking into setting it up

@gotjoshua
Copy link

wondering if anyone managed to get grist working with https://github.com/logto-io/logto ? seems like it could be another option

@anaisconce anaisconce removed this from Researching in Roadmap for Grist Oct 23, 2023
@mishaoljaca
Copy link

Hello community,

I am currently working on setting up SAML-based Single Sign-On (SSO) between Grist and Keycloak. However, I am encountering issues, and I could use some assistance in troubleshooting the configuration.

Key Points:

I have obtained the SAML metadata from Keycloak, which includes the necessary information for configuring Grist.

When attempting to log in to Grist, I encounter the error: "SAML Assertion signature check failed! (checked 1 certificate(s))."

I have verified the system clocks on both Keycloak and Grist servers to ensure they are synchronized.

I have pasted the relevant sections of the Keycloak SAML metadata, and I don't see any settings in Grist's "Identity Providers" tab.

There is no option to set "Login with SAML" in the realm settings under the "Login" tab in Keycloak.

Request for Help:

Can someone guide me on the correct steps to configure Grist with the Keycloak SAML metadata?
Are there specific settings in Grist that I might be missing?
How can I troubleshoot and resolve the "SAML Assertion signature check failed" error?
I appreciate any insights or advice from the community. Thank you!

@dsagal
Copy link
Member

dsagal commented Dec 4, 2023

I found a few items to look into here: Clever/saml2#106. It's not very recent, but maybe something there could help?

@jordigh jordigh added the self-hosting Self-hosting setup that needs some love label May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
self-hosting Self-hosting setup that needs some love wishlist
Projects
Development

No branches or pull requests