Configure SSO on Synology

Hello

Could you please help with configuring SSO?
I haven’t found any article about this.

I’ve installed Grist on Synology in Docker and have Synology SSO server installed but don’t know how to connect them together.

1 Like

Hi @amalcev, thanks for posting! Is this page describing the SSO you have?

It says SAML 2.0 and OpenID Connect (OIDC) are not supported. Grist so far has only been used via SAML. On a quick skim, one way of connecting Grist with Synology would be for someone to write some code that uses SSO’s api (https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/SSOServer/All/enu/Synology_SSO_API_Guide.pdf maybe?) to implement the few methods Grist needs for a new kind of integration https://github.com/gristlabs/grist-core/blob/d51180d349a63972ed9f742df1d9da2064ac95b0/app/server/lib/GristServer.ts#L42

Maybe there’s a way to use SAML, and I missed it - if so the settings are like these: https://github.com/gristlabs/grist-core/blob/main/app/server/lib/SamlConfig.ts#L2

If you or someone you know is a Synology guru, I’d be happy to help with the Grist side of this.

1 Like

Synology SSO Server support OAuth2.0
Description of the setting in Russian lang

Code for PHP implementation on GitHub

Hello, with the release of DSM 7.2 vs, we can use Synology NAS as SAML account provider.
I ask for help from more experienced users, how to add the application correctly?

  • Application name = grist.app

  • Redirect URI: The client app’s URL to which SSO Server redirects users after confirming SAML assertions. This is often referred to as the Assertion Consumer Service URL or ACS URL. = https://grist.app/saml/assert

  • Application ID: The unique identifier of your client app. This is often referred to as the SP Entity ID or Audience URI. = https://grist.app/saml/assert

  • Name ID format: Determines the format of name IDs in SAML assertions. Select Unspecified unless your client app requires a specific format.
    image
    from list
    image

  • Default name ID: Defines the default value used for identifying users on your app. This will be used in a SAML assertion’s Subject statement. =
    Email Addres

  • Attribute (Optional): Map attributes to link the users of Synology’s SSO Server and your client app. You can skip this step unless your app requires additional user information.
    No added attriutes

On Grist Container change only (instead of the standard for Authentik):
GRIST_SAML_IDP_CERTS=synossoserver_saml.crt
GRIST_SAML_IDP_LOGIN=https://mynas.sso.server/sso/webman/sso/SSOOauth.cgi
GRIST_SAML_IDP_LOGOUT=https://mynas.sso.server/sso/webman/sso/SSOOauth.cgi

When I try to login in Grist, I see the following message:
Invalid SSO client application. Open the Application List page and try adding the application again.

I’m glad that Grist is switching sides https://mynas.sso.server/sso/webman/sso/SSOOauth.cgi

Should I change GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT (I have old ones from Authentik) to others, as far as I understand it for data encryption on the Grist side?

Application name: grist
Redirect URI: https://grist-host/saml/assert
Application ID: https://grist-host/saml/metadata.xml
Name ID format: Unspecified
Default name ID: UserId
This configuration can be logged in using SSO, but in the login stage prompt “SessionIndex not an attribute of AuthnStatement.”

Hmm we use a library called Saml2, and I see this issue filed for it:

I see that the issue was solved by adding a flag to control whether SessionIndex should be required or not:

@dmitry-grist do you know it would be reasonable to set that flag to false? I see the index being passed around in our code, but I’m not exactly sure what its role is.

I think it would be reasonable to allow making it optional via optional environment variable (e.g. GRIST_SAML_IDP_SESSION_INDEX_OPTIONAL?)

It might be OK to always treat it as optional, but if the identity provider is expected to always set it, it might be preferable to assert its presence. In my understanding, the session index is received by Grist at login, and provided by Grist at logout, to ensure we are logging out of the correct session when the IdP may support more than one active session for a user.

Synology SSO Server OIDC
Hello Grist Team!
Over the weekend I figured out the Grist authentication settings using the OIDC protocol, when sign-in I go to the Synology SSO Server website and enter my login and password, I get a reload of the Synology SSO Server page and I can’t navigate to the GRIST page.

Synology setup:
OIDC server:
Well-known URL https://sso.synology.host/webman/sso/.well-known/openid-configuration

{
authorization_endpoint: "https://sso.synology.host/webman/sso/SSOOauth.cgi",
claims_supported: [
"aud",
"email",
"exp",
"groups",
"iat",
"iss",
"sub",
"username"
],
code_challenge_methods_supported: [
"S256",
"plain"
],
grant_types_supported: [
"authorization_code",
"implicit"
],
id_token_signing_alg_values_supported: [
"RS256"
],
issuer: "https://sso.synology.host/webman/sso",
jwks_uri: "https://sso.synology.host/webman/sso/openid-jwks.json",
response_types_supported: [
"code",
"code id_token",
"id_token",
"id_token token"
],
scopes_supported: [
"email",
"groups",
"openid"
],
subject_types_supported: [
"public"
],
token_endpoint: "https://sso.synology.host/webman/sso/SSOAccessToken.cgi",
token_endpoint_auth_methods_supported: [
"client_secret_basic",
"client_secret_post"
],
userinfo_endpoint: "https://sso.synology.host/webman/sso/SSOUserInfo.cgi"
}

Application:
App name: grist-host
Redirect URI: https://grist.host/oauth2/callback
App ID: XXXXX
App Secret: YYYYY

GRIST ENV:
GRIST_OIDC_SP_HOST: https://grist.host
GRIST_OIDC_IDP_ISSUER: https://sso.synology.host/webman/sso/.well-known/openid-configuration
GRIST_OIDC_IDP_CLIENT_ID: "XXXXX"
GRIST_OIDC_IDP_CLIENT_SECRET: "YYYYY"
GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT: true
GRIST_OIDC_IDP_SCOPES: "email openid profile"

I ask the experts for help please.

I chked synology sso server on https://openidconnect.net/ with scopes: “openid email”
SSO Server return valid token and correct Decoded Token Payload.
May be problem with incorrect callback URI bcs when i go to https://grist.host real url is https://grist.host/o/docs/?

Pinging @Florent_F as the OIDC expert, any thoughts on @BiBo’s problem Florent?

Hello @BiBo,

Have you noticed any logs related to OIDC in your Grist server and your Synology?

Thank you for feedback.
No, on grist side

Log
date	stream	content
2024/03/29 15:24:22	stdout	2024-03-29 10:24:22.796 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:24:21	stdout	2024-03-29 10:24:21.714 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/active customHostSession=, method=GET, host=grist.host, path=/session/access/active, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:24:21	stdout	2024-03-29 10:24:21.696 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/all customHostSession=, method=GET, host=grist.host, path=/session/access/all, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:24:20	stdout	2024-03-29 10:24:20.830 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:24:19	stdout	2024-03-29 10:24:19.758 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:24:00	stdout	2024-03-29 10:24:00.826 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:23:59	stdout	2024-03-29 10:23:59.736 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/all customHostSession=, method=GET, host=grist.host, path=/session/access/all, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:23:59	stdout	2024-03-29 10:23:59.729 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/active customHostSession=, method=GET, host=grist.host, path=/session/access/active, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:23:58	stdout	2024-03-29 10:23:58.920 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:23:58	stdout	2024-03-29 10:23:58.112 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:21:57	stdout	2024-03-29 10:21:57.169 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:21:56	stdout	2024-03-29 10:21:56.048 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/active customHostSession=, method=GET, host=grist.host, path=/session/access/active, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:21:56	stdout	2024-03-29 10:21:56.039 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/all customHostSession=, method=GET, host=grist.host, path=/session/access/all, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:21:55	stdout	2024-03-29 10:21:55.332 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:21:54	stdout	2024-03-29 10:21:54.320 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:18:25	stdout	2024-03-29 10:18:25.316 - e[34mdebuge[39m: Auth[GET]: grist.host /orgs/0/workspaces customHostSession=, method=GET, host=grist.host, path=/orgs/0/workspaces, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:18:24	stdout	2024-03-29 10:18:24.132 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/all customHostSession=, method=GET, host=grist.host, path=/session/access/all, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:18:24	stdout	2024-03-29 10:18:24.122 - e[34mdebuge[39m: Auth[GET]: grist.host /session/access/active customHostSession=, method=GET, host=grist.host, path=/session/access/active, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:18:22	stdout	2024-03-29 10:18:22.801 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=docs, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 15:18:22	stdout	2024-03-29 10:18:22.301 - e[34mdebuge[39m: Redirecting anonymous user to: https://grist.host/o/docs/
2024/03/29 15:18:22	stdout	2024-03-29 10:18:22.297 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=, email=anon@getgrist.com, userId=1, altSessionId=dJkP94FwEZuH5FkuRrziSw
2024/03/29 14:40:41	stdout	2024-03-29 09:40:41.302 - e[32minfoe[39m: activity docCount=1, orgCount=2, orgInGoodStandingCount=2, userCount=6, userWithLoginCount=5
2024/03/29 13:40:41	stdout	2024-03-29 08:40:41.289 - e[32minfoe[39m: activity docCount=1, orgCount=2, orgInGoodStandingCount=2, userCount=6, userWithLoginCount=5
2024/03/29 13:34:26	stdout	2024-03-29 08:34:26.536 - e[34mdebuge[39m: Redirecting anonymous user to: https://grist.host/o/docs/
2024/03/29 13:34:26	stdout	2024-03-29 08:34:26.494 - e[34mdebuge[39m: Auth[GET]: grist.host / customHostSession=, method=GET, host=grist.host, path=/, org=, email=anon@getgrist.com, userId=1, altSessionId=3cC8VKZxNNw7ZJa8s6eG8b
2024/03/28 04:40:41	stdout	2024-03-27 23:40:41.223 - e[32minfoe[39m: activity docCount=1, orgCount=2, orgInGoodStandingCount=2, userCount=6, userWithLoginCount=5
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.334 - e[32minfoe[39m: activity docCount=1, orgCount=2, orgInGoodStandingCount=2, userCount=6, userWithLoginCount=5
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.332 - e[32minfoe[39m: == grist.externalStorage.active: false
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.331 - e[32minfoe[39m: == grist.externalStorage.disable: - [GRIST_DISABLE_S3]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.330 - e[32minfoe[39m: == grist.externalStorage.minio.bucket: - [GRIST_DOCS_MINIO_BUCKET]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.329 - e[32minfoe[39m: == grist.boot.key: - [GRIST_BOOT_KEY]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.328 - e[32minfoe[39m: == grist.login.forced: - [GRIST_FORCE_LOGIN]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.328 - e[32minfoe[39m: == grist.login.skipSession: - [GRIST_IGNORE_SESSION]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.326 - e[32minfoe[39m: == grist.login.system.oidc.ignoreEmailVerified: false [default] [GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.325 - e[32minfoe[39m: == grist.login.system.oidc.skipEndSessionEndpoint: true [GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.323 - e[32minfoe[39m: == grist.login.system.oidc.endSessionEndpoint:  [default] [GRIST_OIDC_IDP_END_SESSION_ENDPOINT]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.323 - e[32minfoe[39m: == grist.login.system.oidc.emailPropertyKey: email [default] [GRIST_OIDC_SP_PROFILE_EMAIL_ATTR]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.323 - e[32minfoe[39m: == grist.login.system.oidc.namePropertyKey: - [GRIST_OIDC_SP_PROFILE_NAME_ATTR]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.321 - e[32minfoe[39m: == grist.login.system.oidc.clientSecret: ***** [GRIST_OIDC_IDP_CLIENT_SECRET]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.321 - e[32minfoe[39m: == grist.login.system.oidc.clientId: "8cd5e3efd531cc114bff9f62dcfe6b0b" [GRIST_OIDC_IDP_CLIENT_ID]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.320 - e[32minfoe[39m: == grist.login.system.oidc.issuer: https://sso.synology.host/webman/sso/.well-known/openid-configuration [GRIST_OIDC_IDP_ISSUER]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.320 - e[32minfoe[39m: == grist.login.system.oidc.spHost: https://grist.host [GRIST_OIDC_SP_HOST]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.317 - e[32minfoe[39m: == grist.locale.offerAllLanguages: - [GRIST_OFFER_ALL_LANGUAGES]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.317 - e[32minfoe[39m: == grist.integrations.proxy: - [GRIST_HTTPS_PROXY]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.316 - e[32minfoe[39m: == grist.integrations.allowedWebhookDomains: - [ALLOWED_WEBHOOK_DOMAINS]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.315 - e[32minfoe[39m: == grist.integrations.sql.timeout: 1000 [default] [GRIST_SQL_TIMEOUT_MSEC]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.314 - e[32minfoe[39m: == grist.access.listPublicSites: false [default] [GRIST_LIST_PUBLIC_SITES]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.313 - e[32minfoe[39m: == grist.access.supportEmail: support@getgrist.com [default] [GRIST_SUPPORT_EMAIL]
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.308 - e[32minfoe[39m: == willServePlugins: true
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.308 - e[32minfoe[39m: == pluginUrl: http://0.0.0.0:38030/
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.307 - e[32minfoe[39m: == docWorkerId: testDocWorkerId_8484
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.307 - e[32minfoe[39m: == loginMiddlewareComment: oidc
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.306 - e[32minfoe[39m: == bundledRoot: /grist/node_modules/@gristlabs/grist-widget/dist
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.306 - e[32minfoe[39m: == userRoot: /grist/.grist
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.305 - e[32minfoe[39m: == database: sqlite:///persist/home.sqlite3
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.305 - e[32minfoe[39m: == tag: unknown
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.305 - e[32minfoe[39m: == instanceRoot: /persist
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.305 - e[32minfoe[39m: == defaultBaseDomain: grist.host
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.305 - e[32minfoe[39m: == docsRoot: /erp
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.304 - e[32minfoe[39m: == i18:namespace: client,server
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.302 - e[32minfoe[39m: == appRoot: /grist
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.291 - e[32minfoe[39m: pluginServer available at 0.0.0.0:38030
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.289 - e[32minfoe[39m: Server timeouts: keepAliveTimeout 305000 headersTimeout 306000
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.261 - e[33mwarne[39m: Failed to create GoogleAuth endpoint: GOOGLE_CLIENT_SECRET is not defined
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.202 - e[32minfoe[39m: server(home,docs,static) available at 0.0.0.0:8484
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.194 - e[32minfoe[39m: Server timeouts: keepAliveTimeout 305000 headersTimeout 306000
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.147 - e[34mdebuge[39m: PLUGIN bundled/grist-bundled -- /grist/node_modules/@gristlabs/grist-widget/dist/plugins/grist-bundled
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.145 - e[34mdebuge[39m: PLUGIN builtIn/core -- /grist/plugins/core
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.143 - e[32minfoe[39m: Found 2 valid plugins on the system
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.110 - e[32minfoe[39m: No plugins found in directory: /grist/.grist/plugins
2024/03/28 03:40:41	stdout	2024-03-27 22:40:41.045 - e[33mwarne[39m: did not find an appropriately named example workspace in deployment
2024/03/28 03:40:40	stdout	2024-03-27 22:40:40.989 - e[32minfoe[39m: OIDCConfig: initialized with issuer https://sso.synology.host/webman/sso/.well-known/openid-configuration
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.781 - e[32minfoe[39m: Loading empty config because /persist/config.json missing
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.706 - e[34mdebuge[39m: skipping incomplete language th (set GRIST_OFFER_ALL_LANGUAGES if you want it)
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.664 - e[34mdebuge[39m: skipping incomplete language nl (set GRIST_OFFER_ALL_LANGUAGES if you want it)
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.611 - e[34mdebuge[39m: skipping incomplete language fa (set GRIST_OFFER_ALL_LANGUAGES if you want it)
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.497 - e[34mdebuge[39m: skipping incomplete language cs (set GRIST_OFFER_ALL_LANGUAGES if you want it)
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.438 - e[34mdebuge[39m: skipping incomplete language ar (set GRIST_OFFER_ALL_LANGUAGES if you want it)
2024/03/28 03:40:39	stdout	2024-03-27 22:40:39.320 - e[32minfoe[39m: == Grist version is 1.1.12 (commit unknown)
2024/03/28 03:40:39	stdout	Database setup complete.
2024/03/28 03:40:37	stdout	Setting up database...
2024/03/28 03:40:37	stdout	For full logs, re-run with DEBUG=1
2024/03/28 03:40:37	stdout	In quiet mode, see http://localhost:8484 to use.
2024/03/28 03:40:37	stdout	Welcome to Grist.

On SSO Server side
No Error logs