Abstract
When a dependency breaks and upstream has no fix, you have two options: file a ticket and wait, or solve it yourself. AI-assisted development makes the second option viable for practitioners who understand the problem but have never worked in the language or framework involved. This paper demonstrates that approach – from identifying a broken authentication flow to shipping a secure replacement extension, using AI to handle the implementation while the human directs the security architecture.
Our team’s jump box – Apache Guacamole – could not authenticate against our identity provider because its built-in OpenID Connect extension hard-codes the OAuth2 implicit flow, a grant type the IETF has deprecated and modern providers increasingly reject. The only working alternative was LDAP with a separate password. We evaluated every available option – an oauth2-proxy sidecar (abandoned based on prior experience) and a third-party extension (built for an older Guacamole version and unusable out of the box) – before turning to AI-assisted development to fork and fix the third-party extension ourselves. During that process, we discovered a CSRF vulnerability in the original code: state validation logic existed but was never called. This paper documents the investigation, the security analysis that caught a vulnerability automated scanners would have missed, and the authorization code flow replacement with server-side token exchange and CSRF protection.
1. Background
1.1 OAuth2 Grant Types
The OAuth 2.0 Authorization Framework (RFC 6749) defines several grant types for obtaining access tokens. Two are relevant to browser-based applications:
Implicit Flow (Section 4.2): The authorization
server returns the access token directly in the URL fragment
(#access_token=...) after user authentication. The client
(browser JavaScript) extracts the token from the fragment. No client
secret is used, and no server-side token exchange occurs.
Authorization Code Flow (Section 4.1): The
authorization server returns a short-lived authorization code in the URL
query parameter (?code=...). The client’s backend server
exchanges this code for an access token using a confidential HTTP POST
that includes the client secret. The access token never appears in the
browser.
1.2 Why Implicit Flow Is Deprecated
The IETF’s “OAuth 2.0 Security Best Current Practice” (draft-ietf-oauth-security-topics) explicitly recommends against the implicit flow for several reasons:
- Token exposure in URL fragments: Access tokens in fragments are visible in browser history, referrer headers, and browser extensions
- No client authentication: Without a client secret, the authorization server cannot verify that the token is being requested by the legitimate application
- No refresh tokens: The implicit flow cannot issue refresh tokens, requiring re-authentication when the access token expires
- Fragment handling fragility: URL fragment parsing depends on parameter ordering and encoding, which varies across identity providers
The recommended replacement is the authorization code flow, optionally with PKCE (Proof Key for Code Exchange) for public clients.
1.3 Apache Guacamole’s Authentication Architecture
Apache Guacamole is a clientless remote desktop gateway that runs in
a web browser. It provides RDP, SSH, VNC, and Telnet access through a
browser-based client with no plugins or local software required. It
supports pluggable authentication via Java extensions loaded from
/etc/guacamole/extensions/. The built-in
guacamole-auth-sso extension provides OpenID Connect
authentication using the implicit flow.
1.4 Motivation: From Filing Tickets to Fixing Problems
This investigation began with a practical problem: Guacamole serves as our team’s jump box for RDP and SSH access to internal infrastructure. Our identity provider is Kanidm, which maintains LDAP credentials separately from SSO/OAuth credentials – they are different passwords, managed through different workflows.
The built-in Guacamole authentication options that could integrate with Kanidm were LDAP and OpenID Connect. LDAP authentication worked, but it required users to maintain and remember a separate LDAP password distinct from their SSO credentials used for every other internal service. For a jump box that every team member uses daily, this created exactly the kind of password fatigue that SSO is designed to eliminate – users logging in with one password for Guacamole and a different one for everything else.
The built-in OpenID Connect extension should have solved this, but
Kanidm rejects the implicit flow (response_type=id_token)
entirely, returning unsupported_response_type. This is not
a Kanidm-specific limitation – it is the direction the industry is
moving. The built-in extension’s hard-coded use of a deprecated grant
type meant Guacamole could not participate in our SSO infrastructure at
all.
With no configuration option to switch to the authorization code flow (GUACAMOLE-1094), and the upstream bugs open without resolution, we evaluated every available alternative before building our own (see Section 3).
2. Vulnerability Analysis
Three upstream bugs document the deficiencies in Guacamole’s built-in OpenID Connect extension:
2.1 GUACAMOLE-1200: Deprecated Implicit Flow
The built-in extension hardcodes the implicit flow by requesting
response_type=id_token. This is the OAuth2 implicit grant –
the identity provider returns the token directly in the URL fragment
without a server-side exchange.
Impact: Modern identity providers increasingly
refuse to issue tokens via implicit flow. Kanidm, certain Keycloak
configurations, and some cloud identity platforms reject
response_type=id_token entirely, returning an
unsupported_response_type error. Organizations using these
identity providers cannot integrate Guacamole with their SSO
infrastructure.
Security concern: Even when the implicit flow
succeeds, the id_token appears in the browser’s URL
fragment. This token is accessible to browser extensions, visible in
browser history, and can leak through referrer headers if the page
navigates to an external resource.
2.2 GUACAMOLE-1094: Hard-Coded Response Type
The response_type=id_token value is hard-coded in the
built-in extension with no configuration option to override it. This
prevents administrators from switching to the authorization code flow
even if their identity provider supports it.
Impact: AWS Cognito and Google Cloud Identity
Platform reject the id_token response type. These providers
require response_type=code for server-side exchange.
Guacamole cannot authenticate against these widely-used identity
platforms without source modification.
2.3 GUACAMOLE-805: URL Fragment Parsing Failures
The built-in extension’s client-side JavaScript extracts the token
from the URL fragment by assuming a specific parameter ordering. When an
identity provider returns parameters in a different order (e.g.,
#token_type=Bearer&id_token=... instead of
#id_token=...&token_type=Bearer), the parsing fails and
the extension enters an infinite redirect loop.
Impact: The extension works only with identity providers that happen to return parameters in the expected order. This is not specified by any standard – parameter ordering in URL fragments is implementation-defined.
3. Investigation
3.1 Confirming the Incompatibility
Kanidm’s server logs confirmed the root cause directly:
WARN Unsupported OAuth2 response_type (should be 'code') | event_tag_id: 2
ERROR Unable to authorise - error: unsupported_response_type
Guacamole 1.6.0 sends response_type=id_token. Kanidm
only accepts response_type=code. No configuration change on
either side can bridge this – the built-in extension’s grant type is
hard-coded and the identity provider’s requirement is a policy
decision.
3.2 Evaluating Alternatives
Three options were identified:
oauth2-proxy + HTTP header auth – Run oauth2-proxy as a reverse proxy sidecar that handles the OIDC code flow, passing the authenticated username to Guacamole’s
auth-headerextension via HTTP header. This was a battle-tested approach, but we had tried oauth2-proxy for another internal service and abandoned the effort. The added infrastructure complexity – a separate proxy container, header injection, trust boundary configuration – was significant for what should have been a solved problem.merterhk/guacamole-auth-sso-oauth2 – A third-party Guacamole extension implementing the OAuth2 authorization code flow. Initially dismissed – the project had zero stars, zero forks, five total commits, and a single release (v1.5.5). Our Guacamole installation runs version 1.6.0, and the extension was built against 1.5.5 with dependencies pinned to the older release. It could not be used as-is.
SAML – Guacamole has a built-in SAML extension, but Kanidm only supports OAuth2/OIDC. Dead end.
With oauth2-proxy dismissed based on prior experience and the third-party extension incompatible with our Guacamole version, we had no working path to SSO. The choice was: accept LDAP with a separate password, or try to port the third-party extension to 1.6.0 ourselves.
3.3 Deciding to Fork
Despite its minimal community presence, the merterhk extension’s source code was clean and small – seven Java files implementing a straightforward authorization code flow. An AI-assisted source review confirmed the code was well-structured and the porting effort was feasible: update the Maven POM to target Guacamole 1.6.0 dependencies, update the manifest version, and rebuild.
This was the point where AI-assisted development became the path forward. We could not use the extension as-is, but we could fork it and make it work.
3.4 Discovering the CSRF Vulnerability
During the fork and porting process, the AI-assisted security review
identified a vulnerability in the original extension: the
StateService class contained an isValid()
method for CSRF state validation, but the method was never called in the
authentication callback flow. The state parameter was generated and
included in the authorization redirect, but on callback, the returned
state was never validated against the stored value. CSRF protection was
dead code.
CSRF (Cross-Site Request Forgery) in an OAuth2 context means an
attacker can craft a malicious callback URL containing the attacker’s
authorization code. If a victim clicks this link while authenticated,
their Guacamole session becomes linked to the attacker’s identity
provider account – the attacker effectively hijacks the victim’s
session. The state parameter prevents this: the server
generates a random token before redirect, includes it in the
authorization URL, and validates that the same token returns on
callback. Without validation, the protection is meaningless.
Had we deployed the extension without inspecting the source, we would have introduced a CSRF vulnerability into our authentication infrastructure. The extension would have “worked” – users could log in via SSO – but the authentication flow would have been exploitable.
3.5 The Dependency Scanning Gap
No public CVE has been filed for the CSRF vulnerability in the original extension. No security advisory exists on the GitHub repository. Tools like Snyk, Sonatype OSS Index, and GitHub Dependabot track known CVEs in published packages – they would not flag this extension because it has no CVE entries and is not published to Maven Central. SAST tools (like Semgrep or SonarQube) running against the source code could potentially detect dead validation code with the right rules, but these tools are rarely applied to small third-party extensions before deployment.
This is a common gap in practice: organizations deploy third-party extensions, plugins, and libraries from GitHub without security review because automated scanners report “no known vulnerabilities” – which only means no CVE has been filed, not that the code is secure. The CSRF gap was caught because the project’s agent governance rules directed the AI to look for it – verification skills and review checklists that encode security expectations into repeatable agent behavior (see Section 7.2). The AI did not independently decide to check for dead CSRF code; the human’s governance stack made it a required step. An organization that deployed the extension without directed review – relying only on “no CVE” as a proxy for “secure” – would have missed it entirely.
For teams deploying third-party authentication extensions: automated dependency scanning is necessary but not sufficient. Authentication code in particular – where a logic flaw can compromise the entire authorization chain – warrants manual or AI-assisted source review before production deployment.
3.6 Fork, Fix, Build
The extension was forked to Gerry9000/guacamole-auth-sso-oauth2. Changes:
- CSRF state validation integrated into the authentication flow – the state parameter is now generated before redirect and validated on callback, with single-use enforcement and time-limited validity
- Dependencies updated for Guacamole 1.6.0
- i18n resources restored – the login button styling and translations come from the SSO base JAR and had been dropped
The build required Docker – there was no local Java or Maven
toolchain. Guacamole extensions must be Java JARs loaded by Tomcat;
there is no alternative language option. The build script extracts
guacamole-auth-sso-base from the official Guacamole Docker
image, installs it as a local Maven dependency, and builds the extension
JAR with the Maven Shade plugin.
3.7 Deployment and Verification
Deployment is a single JAR:
- Copy
guacamole-auth-sso-oauth2-1.6.0.jarto/etc/guacamole/extensions/ - Configure
guacamole.propertieswith identity provider endpoints and client credentials - Restart Guacamole
After deployment, Kanidm SSO authentication worked. Team members now use the same SSO credentials for Guacamole as for every other internal service. The LDAP password is no longer needed.
4. Security Architecture of the Replacement
The replacement module implements the OAuth2 Authorization Code Flow with the following security properties:
4.1 Server-Side Token Exchange
The authorization code flow moves token handling to the server:
sequenceDiagram
participant Browser as User Browser
participant Guacamole as Guacamole Backend
participant Kanidm as Kanidm (IdP)
Browser->>Guacamole: Click "Login with SSO"
Note over Guacamole: Generate cryptographic state
Guacamole->>Browser: HTTP 302 Redirect
Browser->>Kanidm: GET /auth?response_type=code&state=abc
Kanidm-->>Browser: Present Login Screen
Browser->>Kanidm: User Authenticates
Kanidm->>Browser: HTTP 302 Redirect
Browser->>Guacamole: GET /callback?code=XYZ&state=abc
rect rgb(200, 255, 200)
Note over Guacamole, Kanidm: CONFIDENTIAL BACK-CHANNEL EXCHANGE
Guacamole->>Kanidm: POST /token (code + client_secret)
Kanidm-->>Guacamole: Access Token & ID Token
Guacamole->>Kanidm: GET /userinfo (Bearer Token)
Kanidm-->>Guacamole: User Claims (groups, username)
end
Guacamole-->>Browser: Application Session Cookie Established
The Authorization Code Flow, illustrating the back-channel server exchange that prevents token exposure in the browser.
The client secret and access token never appear in the browser. The authorization code is single-use and short-lived – the lifetime is controlled by the identity provider (Kanidm defaults to 60 seconds; other providers vary, typically 30-120 seconds). The extension itself does not enforce a code lifetime; it relies on the identity provider to reject expired codes during the exchange.
4.2 CSRF Protection
flowchart TD
A[Callback Received] --> B{State Parameter Present?}
B -- No --> C[Reject Authentication]
B -- Yes --> D[Lookup State in ConcurrentHashMap]
D --> E{State Exists?}
E -- No --> F[Reject: Potential CSRF Attack]
E -- Yes --> G[Remove State from Map]
G --> H{State Expired?}
H -- Yes --> I[Reject: Timeout]
H -- No --> J[Proceed to Token Exchange]
style F fill:#ffcccc,stroke:#ff0000
style I fill:#ffcccc,stroke:#ff0000
style J fill:#ccffcc,stroke:#00aa00
The cryptographic state generation and callback validation loop required to prevent Cross-Site Request Forgery during the authentication redirect.
The module implements CSRF protection using cryptographically random state tokens:
public String generateState() {
String state = new BigInteger(130, random).toString(32);
StateEntry entry = new StateEntry(state, Instant.now());
pendingStates.put(state, entry);
return state;
}
Security properties of the state management: - Cryptographic
randomness: 130-bit tokens generated from
SecureRandom, yielding base-32 strings with 130 bits of
entropy - Single-use enforcement: States are removed
from the map immediately upon first validation, preventing replay
attacks - Time-limited validity: States expire after a
configurable maximum age (default 10 minutes), with a background cleanup
sweep every 60 seconds - Thread-safe storage:
ConcurrentHashMap handles concurrent requests from multiple
browser sessions
Why SecureRandom: Java provides two
random number generators – java.util.Random and
java.security.SecureRandom. Random uses a
deterministic pseudorandom number generator (a linear congruential
formula) that is predictable if the seed is known.
SecureRandom draws from the operating system’s entropy
source (typically /dev/urandom on Linux) and produces
cryptographically unpredictable output. For security tokens – CSRF
state, session IDs, nonces, and cryptographic keys –
SecureRandom is the only appropriate choice. Using
Random or Math.random() for security tokens is
a well-known vulnerability class that allows attackers to predict future
tokens by observing past ones.
4.3 Token Validation and Userinfo Retrieval
After exchanging the authorization code for an access token, the module retrieves user information from the identity provider’s userinfo endpoint:
connection.setRequestProperty("Authorization", "Bearer " + accessToken);
Security properties: - Bearer token authentication: Proper OAuth2 authorization header usage (not query parameter) - Timeout enforcement: 10-second connect and read timeouts prevent hanging requests from blocking the authentication pipeline - No token logging: Access tokens and userinfo responses are not written to Guacamole’s logs, preventing credential exposure in log files - Response validation: HTTP status codes are checked before parsing, with appropriate error messages for non-200 responses
4.4 Configuration Properties
The module exposes configuration through
guacamole.properties:
| Property | Required | Description |
|---|---|---|
oauth2-authorization-endpoint |
Yes | Authorization URL |
oauth2-token-endpoint |
Yes | Token exchange URL |
oauth2-userinfo-endpoint |
Yes | Userinfo retrieval URL |
oauth2-client-id |
Yes | OAuth2 client identifier |
oauth2-client-secret |
Yes | Client secret (server-side only) |
oauth2-redirect-uri |
Yes | Callback URL |
oauth2-scope |
No | Requested scopes (default: email profile) |
oauth2-username-claim |
No | Userinfo field for username (default: username) |
oauth2-groups-claim |
No | Userinfo field for group membership (default:
groups) |
oauth2-max-state-validity |
No | State token max age in minutes (default: 10) |
The client secret is stored in a server-side configuration file with restricted permissions, not in client-side code or URL parameters.
5. Comparison of Security Properties
| Property | Implicit Flow (Built-in) | Authorization Code Flow (Replacement) |
|---|---|---|
| Token in browser URL | Yes (fragment) | No (server-side exchange) |
| Client secret used | No | Yes (confidential) |
| CSRF protection | None | Cryptographic state tokens |
| Refresh token support | No | Possible (IdP-dependent) |
| Token logging risk | High (URL in access logs) | Low (tokens not logged) |
| IdP compatibility | Limited (rejects id_token) | Broad (standard code flow) |
| Fragment parsing issues | Yes (order-dependent) | No (query parameter, single value) |
6. Implementation Notes
6.1 Build and Deployment
The module is built with Maven and packaged as a single JAR using the Maven Shade plugin, which bundles the SSO base classes required by Guacamole’s extension API:
- Extract
guacamole-auth-sso-basefrom a running Guacamole installation - Install it to the local Maven repository
- Build with
mvn package - Copy the shaded JAR to
/etc/guacamole/extensions/
6.2 Identity Provider Compatibility
The module has been tested with Kanidm (OAuth2/OIDC
with PKCE support). It uses standard OAuth2 authorization code flow
endpoints and standard userinfo claims, so it should work with any
identity provider that supports the authorization code grant. The
oauth2-username-claim and oauth2-groups-claim
configuration properties allow mapping provider-specific claim names to
Guacamole’s user model.
6.3 Multi-Language Support
The extension includes UI translations for 9 languages (Catalan, German, English, French, Japanese, Korean, Portuguese, Russian, Chinese), matching the localization coverage of the built-in extension.
7. Methodology
7.1 AI-Assisted Development
The companion paper on RDP authentication forensics documents what happens when AI-generated analysis is wrong – fabricated identifiers, incorrect claims about source code, and unnecessary patches that required adversarial verification to identify and remove. This investigation demonstrates the complementary case.
The problem was well-defined: a specific error message
(unsupported_response_type), a known standard (OAuth2
authorization code flow), and existing code to build from. When the
problem is clear and the target specification is known, AI-assisted
development can proceed without the fabrication risk that ambiguous
problems create. The development proceeded cleanly – no fabricated
claims, no incorrect analysis, no unnecessary changes.
7.2 Directing the AI
The AI handled the implementation mechanics: the Guacamole extension API, Maven Shade packaging, OAuth2 token exchange HTTP calls, CSRF state management, multi-language UI translations, and the Docker-based build scripting. The human directed the security architecture: which grant type to use, what CSRF properties to require, how to handle the client secret, what timeout values to set, and what not to log.
This division works because security decisions do not require language-specific expertise. You do not need to know Java to know that CSRF protection requires cryptographic randomness and single-use enforcement, that client secrets must never reach the browser, or that tokens should not appear in log files. The human brings security judgment; the AI brings implementation capability.
In practice, directing the AI means maintaining governance documents
and skills that encode these security expectations. A project-level
CLAUDE.md file that requires the AI to grep before
referencing any identifier, classify findings as VERIFIED or INFERRED,
and justify every line in a diff prevents the most common classes of AI
error. Skills like /verify-claim (verify a technical
assertion against source code before including it in any document) and
/review-diff (adversarial review of a patch before
submission) encode the verification workflow as repeatable commands.
These are not Guacamole-specific – they apply to any AI-assisted
security work. The AI does not inherently know to check for dead CSRF
code; the human’s security review checklist and the project’s
verification rules direct it to look.
7.3 From Filing Tickets to Fixing Problems
The three GUACAMOLE bugs referenced in Section 2 have been open without resolution – GUACAMOLE-805 since 2018, GUACAMOLE-1094 since 2019, GUACAMOLE-1200 since 2020. For the RDP bug documented in the companion paper, related issues had been open across multiple distributions for three years. The traditional path – file an issue and wait – had a multi-year track record of not producing results.
AI-assisted development changes this calculus. A system administrator, DevSecOps engineer, SRE, or security analyst who understands the problem, can evaluate the security implications, and can direct the architecture can now produce a working fix – even in a language and framework they have never worked in. The extension we built is deployed in production and authenticates against a real identity provider. The alternative was continuing to use LDAP with a separate password, or waiting for upstream.
This is not limited to senior security professionals. Anyone who can identify a problem, evaluate alternatives, and apply a security review checklist can direct AI-assisted development. The tools lower the barrier from “must be able to write Java” to “must be able to evaluate whether the Java the AI wrote is secure.” That is a fundamentally different – and much broader – set of practitioners.
8. Conclusion
The implicit flow in Guacamole’s built-in OpenID Connect extension is a liability: it is deprecated by the IETF, incompatible with modern identity providers, exposes tokens in browser URLs, and relies on fragile client-side fragment parsing. The authorization code flow replacement addresses all of these issues while adding CSRF protection and proper server-side credential handling.
For organizations deploying Guacamole as a remote access gateway – particularly in environments subject to compliance requirements (NIST 800-63B, SOC 2, HIPAA) – migrating from implicit to authorization code flow eliminates a known category of token exposure risk. The extension is available at Gerry9000/guacamole-auth-sso-oauth2.
Glossary
Authorization Code Flow: An OAuth2 grant type where the identity provider returns a short-lived authorization code to the browser, which the application’s backend server then exchanges for an access token via a confidential HTTP request. The access token never reaches the browser. This is the IETF-recommended flow for server-side applications.
Bearer Token: An access token that grants access to
a protected resource simply by presenting it – no additional proof of
identity is required. Bearer tokens are typically sent in the HTTP
Authorization header
(Authorization: Bearer <token>). Because possession
alone grants access, bearer tokens must be protected in transit (TLS)
and at rest (server-side storage only).
Client Secret: A confidential credential shared between the application and the identity provider. Used during the authorization code exchange to prove the application’s identity. Must be stored server-side with restricted file permissions – never in client-side code, URL parameters, or browser-accessible storage.
CSRF (Cross-Site Request Forgery): An attack where a
malicious site tricks a user’s browser into making unwanted requests to
a site where the user is authenticated. In the OAuth2 context, a CSRF
attack can inject the attacker’s authorization code into the victim’s
authentication flow, linking the attacker’s identity to the victim’s
session. Prevented by including a cryptographically random
state parameter in the authorization request and validating
it on callback.
Identity Provider (IdP): A service that authenticates users and issues tokens or assertions about their identity. Examples include Kanidm, Keycloak, AWS Cognito, Azure AD/Entra ID, and Okta. In OAuth2, the IdP is the authorization server that issues access tokens.
Implicit Flow: An OAuth2 grant type where the
identity provider returns the access token directly in the URL fragment
(#access_token=...). Deprecated by the IETF due to token
exposure in browser history, referrer headers, and browser extensions,
and the inability to use client secrets or issue refresh tokens.
OAuth2 (Open Authorization 2.0): An authorization framework (RFC 6749) that enables applications to obtain limited access to user accounts on third-party services. OAuth2 defines multiple grant types (authorization code, implicit, client credentials, etc.) for different use cases. It is the foundation for most modern SSO implementations.
OIDC (OpenID Connect): An identity layer built on top of OAuth2 that adds user authentication. While OAuth2 provides authorization (access to resources), OIDC adds authentication (verifying who the user is) by defining an ID token and a standard userinfo endpoint.
PKCE (Proof Key for Code Exchange): An extension to the authorization code flow (RFC 7636) that protects against authorization code interception attacks. The client generates a random code verifier and sends a hashed version (code challenge) with the authorization request. During the code exchange, the client sends the original verifier, and the server validates the hash. Originally designed for mobile apps, now recommended for all public clients.
SecureRandom: Java’s cryptographic random number
generator (java.security.SecureRandom). Unlike
java.util.Random, which uses a predictable pseudorandom
algorithm, SecureRandom draws from the operating system’s
entropy source and produces cryptographically unpredictable output.
Required for generating security tokens, session IDs, and cryptographic
keys.
State Token: A cryptographically random value included in an OAuth2 authorization request and validated on callback. Prevents CSRF attacks by ensuring the callback corresponds to a request initiated by the legitimate application. Must be single-use (consumed after validation), time-limited, and generated with a cryptographic random number generator.
References
- IETF, “The OAuth 2.0 Authorization Framework,” RFC 6749
- IETF, “OAuth 2.0 Security Best Current Practice,” draft-ietf-oauth-security-topics
- IETF, “Proof Key for Code Exchange by OAuth Public Clients,” RFC 7636
- NIST, “Digital Identity Guidelines – Authentication and Lifecycle Management,” SP 800-63B
- Apache Guacamole, “guacamole-auth-sso,” https://github.com/apache/guacamole-client
- GUACAMOLE-1200, “Support OAuth2 Authorization Code Flow,” https://issues.apache.org/jira/browse/GUACAMOLE-1200
- GUACAMOLE-1094, “OpenID response_type should be configurable,” https://issues.apache.org/jira/browse/GUACAMOLE-1094
- GUACAMOLE-805, “OpenID authentication broken with some providers,” https://issues.apache.org/jira/browse/GUACAMOLE-805
- merterhk, “guacamole-auth-sso-oauth2” (original), https://github.com/merterhk/guacamole-auth-sso-oauth2
- Gerry9000, “guacamole-auth-sso-oauth2” (fork), https://github.com/Gerry9000/guacamole-auth-sso-oauth2