grist-core icon indicating copy to clipboard operation
grist-core copied to clipboard

Better support for self-hosting login solution

Open paulfitz opened this issue 4 years ago • 41 comments

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

paulfitz avatar Jul 19 '21 18:07 paulfitz

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.

almereyda avatar Aug 12 '21 13:08 almereyda

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

paulfitz avatar Aug 12 '21 14:08 paulfitz

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 avatar Aug 18 '21 14:08 paulfitz

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

paulfitz avatar Dec 14 '21 19:12 paulfitz

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) ?

FVilli avatar Jan 22 '22 08:01 FVilli

Hi @FVilli, try setting the variables mentioned in https://github.com/gristlabs/grist-core/issues/117#issuecomment-1004358045:

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).

paulfitz avatar Jan 22 '22 13:01 paulfitz

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

Gatherix avatar Feb 17 '22 01:02 Gatherix

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.

jyio avatar Feb 18 '22 01:02 jyio

Thanks a lot for posting that @jyio!

paulfitz avatar Feb 18 '22 15:02 paulfitz

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 :)

apollo13 avatar Feb 19 '22 11:02 apollo13

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 avatar Feb 19 '22 11:02 helmut72

@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.

helmut72 avatar Feb 19 '22 13:02 helmut72

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 /.

jyio avatar Feb 19 '22 15:02 jyio

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.

helmut72 avatar Feb 20 '22 18:02 helmut72

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 (https://github.com/w3c/webappsec-change-password-url/issues/34 , https://github.com/dani-garcia/vaultwarden/discussions/1691 ) but even then it proves to be the top approach to login management.

dumblob avatar Feb 21 '22 10:02 dumblob

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

helmut72 avatar Feb 21 '22 11:02 helmut72

@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.

dumblob avatar Feb 21 '22 11:02 dumblob

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.

jyio avatar Feb 21 '22 15:02 jyio

@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.

helmut72 avatar Feb 22 '22 19:02 helmut72

@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

dumblob avatar Feb 23 '22 11:02 dumblob

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:

  • https://github.com/soulteary/docker-sso-server
  • https://github.com/vicalloy/oidc-server

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

almereyda avatar Feb 23 '22 14:02 almereyda

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.

  • https://community.getgrist.com/t/a-template-for-self-hosting-grist-with-traefik-and-docker-compose/856
  • https://github.com/thomseddon/traefik-forward-auth#provider-setup

paulfitz avatar Apr 07 '22 14:04 paulfitz

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.

paulfitz avatar Aug 17 '22 13:08 paulfitz

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

helmut72 avatar Aug 17 '22 14:08 helmut72

@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 https://github.com/gristlabs/grist-core/issues/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.

paulfitz avatar Aug 17 '22 16:08 paulfitz

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?

helmut72 avatar Aug 17 '22 21:08 helmut72

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.

paulfitz avatar Aug 17 '22 21:08 paulfitz

@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.

helmut72 avatar Aug 29 '22 07:08 helmut72

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.

viniciusao avatar Dec 27 '22 13:12 viniciusao

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?

paulfitz avatar Dec 27 '22 15:12 paulfitz