Enables OIDC/OAuth2 login for Grocy using the authorization code flow with PKCE.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Tobias Strobel 46250ba57a
All checks were successful
Release / release (push) Successful in 27s
REUSE compliance check / test (push) Successful in 5s
Add release workflow and REUSE compliance check
2026-09-21 22:42:32 +02:00
.forgejo/workflows Add release workflow and REUSE compliance check 2026-09-21 22:42:32 +02:00
LICENSES Add release workflow and REUSE compliance check 2026-09-21 22:42:32 +02:00
.pre-commit-config.yaml Add release workflow and REUSE compliance check 2026-09-21 22:42:32 +02:00
OidcAuthMiddleware.php Add release workflow and REUSE compliance check 2026-09-21 22:42:32 +02:00
README.md Add release workflow and REUSE compliance check 2026-09-21 22:42:32 +02:00

Grocy OIDC Middleware

This OidcAuthMiddleware enables OIDC/OAuth2 login for Grocy using the authorization code flow with PKCE. This middleware integrates with a Grocy instance version 4.7.0 or higher.

Note: This is not an official part of Grocy. Please report issues here and NOT in the Grocy repo.

Disclaimer: The middleware implementation was mainly created by GitHub Copilot (GPT-5.3-Codex).

This was tested against Pocket ID and should work with OAuth2/OIDC providers that support the authorization code flow.

OIDC auto-discovery is supported via issuer metadata.

Installation & Configuration

  1. Copy the OidcAuthMiddleware.php to the Grocy root at middleware/Auth/OidcAuthMiddleware.php
  2. Configure a OAuth app in your OAuth provider
  3. Add the following settings to your config.php and adapt to your setup:
Setting('AUTH_CLASS', 'Grocy\Middleware\Auth\OidcAuthMiddleware');

// Options when using OidcAuthMiddleware
Setting('OAUTH_CLIENT_ID', '');
Setting('OAUTH_CLIENT_SECRET', '');
Setting('OAUTH_CLIENT_SECRET_FILE', ''); // Alternative when the secret is stored in a file
Setting('OAUTH_ISSUER', 'https://id.example.com');
Setting('OAUTH_REDIRECT_URI', 'https://grocy.example.com/stockoverview');
Setting('OAUTH_FLOW_COOKIE_SECRET', ''); // Use this or OAUTH_FLOW_COOKIE_SECRET_FILE; at least 32 characters
Setting('OAUTH_FLOW_COOKIE_SECRET_FILE', ''); // Alternative secret file; keep readable only by the webserver

// Transport and flow security
Setting('OAUTH_ALLOW_INSECURE_HTTP', false); // Development/debugging only
Setting('OAUTH_CLOCK_SKEW_SECONDS', 60);

// Identity and profile claims
Setting('OAUTH_PROMPT', 'select_account');
Setting('OAUTH_SCOPES', 'openid profile groups');
Setting('OAUTH_USERNAME_CLAIM', 'preferred_username');
Setting('OAUTH_FIRST_NAME_CLAIM', 'given_name');
Setting('OAUTH_LAST_NAME_CLAIM', 'family_name');
Setting('OAUTH_PICTURE_CLAIM', 'picture');
Setting('OAUTH_PICTURE_MAX_BYTES', 2097152);
Setting('OAUTH_PICTURE_ALLOWED_HOSTS', []); // Defaults to the OAUTH_ISSUER host

// Provisioning and group-based permissions
Setting('OAUTH_AUTO_PROVISION', false);
Setting('OAUTH_GROUPS_CLAIM', 'groups');
Setting('OAUTH_ADMIN_GROUP', 'grocy_admins');

With Pocket ID, a minimal setup is typically:

  • OAUTH_CLIENT_ID
  • OAUTH_CLIENT_SECRET or OAUTH_CLIENT_SECRET_FILE (one is required)
  • OAUTH_ISSUER
  • OAUTH_REDIRECT_URI
  • OAUTH_FLOW_COOKIE_SECRET

with the client configured to allow PKCE.

Generate a suitable flow-cookie secret on Linux with:

openssl rand -base64 32

Defaults and fallbacks

  • OAUTH_SCOPES falls back to openid profile groups if not set.
  • The middleware supports confidential clients only; configure either OAUTH_CLIENT_SECRET or OAUTH_CLIENT_SECRET_FILE.
  • OAUTH_CLIENT_SECRET_FILE is used when OAUTH_CLIENT_SECRET is empty.
  • Configure exactly one of OAUTH_FLOW_COOKIE_SECRET and OAUTH_FLOW_COOKIE_SECRET_FILE; the secret must contain at least 32 characters.
  • OAUTH_USERNAME_CLAIM falls back to preferred_username and then tries username, nickname, email, sub.
  • OAUTH_FIRST_NAME_CLAIM falls back to given_name.
  • OAUTH_LAST_NAME_CLAIM falls back to family_name.
  • OAUTH_PICTURE_CLAIM falls back to picture.
  • OAUTH_PICTURE_MAX_BYTES falls back to 2097152 (2 MiB).
  • OAUTH_PICTURE_ALLOWED_HOSTS defaults to the hostname from OAUTH_ISSUER; configure an explicit array to allow a different picture host.
  • OAUTH_AUTO_PROVISION defaults to false; set it to true to create a Grocy user when no matching username exists.
  • OAUTH_GROUPS_CLAIM defaults to groups and identifies the UserInfo claim containing group memberships.
  • OAUTH_ADMIN_GROUP is optional; users in this exact group receive Grocy's ADMIN permission. Users outside the group have only the ADMIN permission removed.
  • Newly provisioned users outside the admin group receive all feature permissions and MASTER_DATA_EDIT, but never user-management permissions or USERS_EDIT_SELF. Later logins preserve any permission changes made by Grocy administrators.
  • OAUTH_CLOCK_SKEW_SECONDS defaults to 60 and controls the accepted clock difference for ID-token timestamps.
  • OAUTH_ALLOW_INSECURE_HTTP defaults to false and must only be enabled for local development or debugging. OAuth endpoints, discovery, JWKS, UserInfo, the redirect URI, and picture downloads require HTTPS otherwise.
  • PKCE with S256 is mandatory and is used together with the confidential-client secret, not as a replacement for it.
  • OAUTH_SCOPES must include openid; the middleware always validates the resulting ID token.
  • OAUTH_PROMPT is always sent as prompt on the authorization request and falls back to select_account if not set.

OIDC discovery behavior

  • OIDC discovery is mandatory. The middleware fetches {issuer}/.well-known/openid-configuration from OAUTH_ISSUER and uses the authorization, token, and UserInfo endpoints returned by the document.
  • Discovery must advertise client_secret_basic in token_endpoint_auth_methods_supported, and its issuer must exactly match OAUTH_ISSUER.

IdP authorization controls

  • Use OAUTH_PROMPT to influence the IdP login behavior (login, consent, select_account, or none, depending on your provider).
  • To prevent silent SSO re-login, keep OAUTH_PROMPT at select_account (recommended default).

Supported

  • OAuth2 authorization code flow.
  • Mandatory PKCE (S256) with server-side verifier handling.
  • PKCE provider capability verification via discovery (code_challenge_methods_supported must include S256).
  • OIDC discovery (/.well-known/openid-configuration) for auth/token/userinfo endpoints.
  • OIDC ID token validation (RS256 signature, iss, aud, exp, nonce, and sub match with userinfo).
  • User provisioning and profile synchronization (username/name/picture claim mapping).
  • Prompt forwarding (prompt) on every authorization request.

Not supported

  • OIDC RP-initiated/back-channel/front-channel logout support.
  • Refresh-token based session renewal.
  • Complex claim transformations beyond direct claim-key mapping and username fallbacks.
  • Non-RS256 ID token algorithms.

Redirect URI guidance

  • Use one fixed redirect URI, configured via OAUTH_REDIRECT_URI, and register that exact URI in your OAuth provider.
  • For Grocy, use a protected non-root, non-login page as callback (for example /stockoverview).
  • Do not use / or /login as callback when using this middleware class.

Profile sync behavior

  • On every successful login, Grocy user profile fields are synchronized from claims.
  • User first_name and last_name are created from claims and updated on subsequent logins when claim values are present.
  • User profile pictures are downloaded from the configured picture claim URL and stored in Grocy userpictures storage; unsupported mime types or failed downloads are ignored.
  • Profile-picture downloads require HTTPS, use the configured host allowlist, and do not follow redirects. The access token is not sent to the picture URL.

Username identity limitation

The middleware maps users by the configured username claim because it cannot add an identity mapping table to Grocy. Changing the IdP username can therefore create a new Grocy user or, if the new username matches another account, cause an account collision. Use an immutable username claim where possible and do not allow users to change it at the IdP.

Development checks

Install and enable the pre-commit hooks with:

python -m pip install pre-commit
pre-commit install
pre-commit run --all-files