SZ-4a: Implement Authentication (OAuth2/OIDC login + JWT token support) (SZ-13)
rk@tigase.net opened 2 weeks ago

This sub-issue focuses on the authentication (AuthN) setup for the Sztab backend using Spring Security and Keycloak.

Scope includes:

  • Configure oauth2Login() for Web UI (/ui/**) via Keycloak
  • Support JWT-based stateless authentication for API (/api/**) using Keycloak-issued tokens
  • Integrate with Spring Security’s SecurityFilterChain
  • Decode and validate JWT tokens in the backend using a custom JwtAuthenticationFilter
  • Populate the authenticated user from the token (Principal, roles, etc.)

Note:

  • Authorization (role-based access control) will be handled in a follow-up sub-issue (SZ-4b)
  • rk@tigase.net commented 2 weeks ago

    SZ-4a: Implement Authentication (OAuth2/OIDC login + JWT token support)

    This sub-issue focuses on the authentication (AuthN) setup for the Sztab backend using Spring Security and Keycloak.

    Scope

    • Configure oauth2Login() for Web UI (/ui/**) via Keycloak
    • Support JWT-based stateless authentication for API (/api/**) using Keycloak-issued tokens
    • Integrate with Spring Security’s SecurityFilterChain
    • Decode and validate JWT tokens in the backend using a custom JwtAuthenticationFilter
    • Populate the authenticated user from the token (Principal, roles, etc.)
    • Leave authorization (role-based access control) for follow-up task (SZ-4b)

    Estimated Effort

    TaskEst. Time
    Configure Keycloak clients (UI + API)0.5h
    Update application.yml with Keycloak OIDC config0.5h
    Implement SecurityConfig.java with two filter chains1.5h
    Create JwtTokenProvider.java (token parser/validator)1h
    Create JwtAuthenticationFilter.java1h
    Test Web UI login flow (oauth2Login + session)0.5h
    Test REST API access with JWT token (manual or Postman)0.5h
    Add basic unit/integration tests for AuthN setup1h
    Write usage notes or dev README0.5h

    Total Estimate: 7 hours

  • rk@tigase.net commented 2 weeks ago

    Work underway in feature/sz-4-spring-security-authn-authz

  • rk@tigase.net changed state to 'In Progress' 2 weeks ago
    Previous Value Current Value
    Open
    In Progress
  • rk@tigase.net commented 2 weeks ago

    Design:

    1. SecurityConfig.java • Two filter chains: • /ui/** → oauth2Login() (OIDC via Keycloak) • /api/** → JWT-based stateless security

    2. application.yml (partial) • Keycloak client config for oauth2Login

    3. JwtTokenProvider.java • Validates and parses tokens from the Authorization header • Uses Keycloak’s public keys (JWKS endpoint)

    4. JwtAuthenticationFilter.java • Intercepts /api/** • Extracts and validates JWT • Populates SecurityContext

  • Andrzej Wójcik (Tigase) commented 2 weeks ago

    @kobit @rk I wonder about this task. I know it may be a little to late, but why/when we decided to user Keycloak (Identity and Access Manager, see https://www.keycloak.org/). I know it is often used, but if we would like to add Sztab to our Tygrys deployment then we would have to deploy Keycloak on each cluster.... I suppose Keycloack could then forward requests (authentication requests) over LDAP to Tigase XMPP Server (LDAP server), but I just wonder if that wouldn't be an overkill for us.

  • Wojciech Kapcia (Tigase) commented 2 weeks ago

    Or rather - we do want to base and build the tools around Tigase and with the added support of being LDAP server wouldn't it be easier to use Spring LDAP for authentication directly without any 3rd party?

  • Artur Hefczyc commented 2 weeks ago

    Definitely, I am in favor of using Tigase/LDAP as primary source of identity and authentication. Other tools/ways can be of course an option added over time.

  • rk@tigase.net commented 2 weeks ago

    Andrzej, thanks for the note - I was going to directly reach out to you as well on the design choices I have made. I chose Keycloak not because it’s the only option, but because it gives us flexibility at a relatively low cost: I was looking not narrowly at our own use case alone, but also the use cases of open source users who look for enterprise capabilities in Sztab.

    Keycloak handles a lot of the complexity that typically creeps into authentication:

    • supports modern authentication flows out of the box: OAuth2, OIDC, SAML2, and PKCE (important for mobile clients).
    • integrates with external identity providers like GitHub, Google, Facebook, corporate LDAPs, or even SAML2 IdPs, without any code changes on our side.
    • manages JWT issuance, token expiry, revocation, 2FA, SSO, and user federation—all through configuration.
    • operationally, it’s light: a single pod with a Postgres backend and well-documented Helm charts.

    Here is a visual:

    keycloak-oidc.png

    In other words, it gives us a clean boundary between identity management and our application logic. That’s especially helpful if Sztab is used outside Tigase or by contributors who want GitHub login, for instance.

    Degenerate Deployment without Keycloak as an option: If we want to dispense with operational cost (however minimal) of using Keycloak (such as with Tigase), we can use the same binary to create a degenerate deployment where Sztab directly talks to Tigase LDAP as shown below:

    00deg.png00deg

    Sztab, based on Spring Boot, is already set up with a pluggable SecurityConfig, and depending on profiles or environment variables, we can:

    • Use spring.security.oauth2.resourceserver.jwt.* for Keycloak+JWT
    • Or, bypass JWT entirely and configure spring.ldap.* and LdapAuthenticationProvider for direct LDAP login

    Spring Security supports both approaches natively, and we can even externalize this decision via a @ConditionalOnProperty or profile-based bean wiring strategy.

    So one binary ==> multiple deployments.

  • rk@tigase.net commented 2 weeks ago

    Or rather - we do want to base and build the tools around Tigase and with the added support of being LDAP server wouldn't it be easier to use Spring LDAP for authentication directly without any 3rd party?

    Wojciech, Spring security does talk Spring LDAP to the external LDAP server: if we use Spring Security, using its configs, it does the required thing: use LDAP to talk to a configured server or use SAML2 to talk to an IdP or use Authorization Code Flow with PKCE for mobile clients or use OAuth/OIDC to talk to OAuth2 servers.

  • rk@tigase.net commented 2 weeks ago

    Hi all,

    I’m proceeding with the current implementation of OAuth2/OIDC authentication via Keycloak, which brings us the flexibility and power of: • Modern authentication flows (Authorization Code, PKCE for mobile, etc.) • Easy integration with external IdPs (Google, GitHub, SAML2, etc.) • Centralized user/role management (with SSO support)

    At the same time, I’ve ensured that the same application binary can run in a degenerate mode, where Keycloak is omitted entirely, and authentication is performed directly against a Tigase LDAP server. This makes Sztab easy to adopt in internal deployments with minimal operational overhead.

    No code duplication or special builds are required — just a different Spring profile or config file.

    Please let me know if there are any concerns before I finalize this track.

    — Rk

  • Andrzej Wójcik (Tigase) commented 2 weeks ago

    My intention was to point that we want to reduce dependencies (especially on external "applications"). In Tygrys we relied on (tried) Mailu, then on Apache James and 1dev. Now Mailu was replaced by James and 1dev is going to be replaced possibly by Sztab. If we introduce additional "dependencies on external applications" we, sooner or later, may end up needing to replace them. Also we need to maintain them (deploy them end upgrade) in Tygrys on possibly many clusters.

    In case of Keycloack, except from "another dependency to deploy and maintain", I wonder about required memory or CPU usage. Do you have any numbers on that?

    Why I mention that? We have "wedding planner" cluster, that is almost empty (no data). It has James, MySQL, 1dev, and Tigase XMPP Server and it uses already 52% of the memory (that is over 11GB) and has over 77% of CPU power allocated (that is on 3 nodes cluster with total of 6 cores and 23GB of RAM). We had plans to offer deployment of the same apps on a single node ("personal" tier) that would have just 2 cores (a lot of cores are allocated to clustered Longhorn so reducing no. of nodes would reduce allocation of CPU cores) and 16GB of RAM. And we still need to have some "spare" resources on the host OS and so on.

    From your comments I know that Keycloack would be optional and that resolves my issues about CPU and memory usage in Tygrys, but still adding optional dependency (even for our own cluster) would force us to add option to Tygrys Admin UI (or whatever we will call this management app in the future).

  • rk@tigase.net commented 2 weeks ago
  • rk@tigase.net commented 2 weeks ago

    Spring Security AuthN Support — Implemented and Merged

    This issue tracks the integration of JWT-based authentication using Spring Security 6 / Spring Boot 3.5.

    Scope covered:

    • Stateless JWT authentication via Spring’s oauth2ResourceServer().jwt() support
    • Integration with Keycloak for token issuance and validation
    • Role-based authorization using .hasRole("...") and @PreAuthorize
    • Default admin user bootstrapped from application.yml
    • Security unit test coverage:
      • Public vs secure endpoint access
      • Role-based access control
      • Custom JWT claim mapping via CustomJwtAuthenticationConverter

    Pull Request: https://tigase.dev/sztab/~pulls/2

    Key files:

    • SecurityConfig.java – Defines the primary security filter chain
    • CustomJwtAuthenticationConverter.java – Maps realm_access.roles to Spring authorities
    • SecurityConfigTest.java – Focused security test with mock JWTs
    • TestSecurityBeans.java and TestSecurityOverride.java – Support test slices

    The feature branch feature/sz-4-spring-security-authn-authz has been merged to wolnosc, and all unit tests are passing.

    Closing this issue as complete. Follow-up enhancements (e.g., fine-grained claims mapping, logout, or multi-tenancy support) can be tracked separately.

    —Rk

  • rk@tigase.net changed state to 'Closed' 2 weeks ago
    Previous Value Current Value
    In Progress
    Closed
  • rk@tigase.net commented 2 weeks ago

    Work Log — Spring Security Authentication (SZ-4)

    This section captures the actual time spent implementing, testing, and documenting JWT-based authentication for the Sztab backend.

    Task DescriptionTime Spent
    Analyzing Spring Security 6 behavior and OAuth2 Resource Server support1.5h
    Designing SecurityConfig and access control rules (permitAll, hasRole)1.0h
    Implementing CustomJwtAuthenticationConverter0.5h
    Writing unit test SecurityConfigTest using @WebMvcTest, mock JWTs2.0h
    Supporting test config beans (TestSecurityBeans, TestSecurityOverride)1.0h
    Verifying Spring profiles, application.yml merging and test slice behavior0.5h
    Final cleanup, file headers, inline documentation, and PR description1.0h
    Total7.5h

    Note: This includes time spent understanding Spring Security test slices, test config override patterns, and minimizing noise for non-security test cases.

  • rk@tigase.net commented 2 weeks ago

    Andrzej,

    I appreciate the concern around keeping the dependency surface minimal, especially in light of past experiences with Mailu, James, and 1dev.

    As you suggest, every external component we adopt must be weighed carefully against operational costs (deployment, upgrades, resource usage). I want to clarify a few points and share some observations that might help put the Keycloak usage in context.

    Keycloak Is Optional and Non-Blocking for Tygrys

    As you noted, Sztab can function without Keycloak — it’s fully usable with internal testing or basic token injection, and the dependency on Keycloak only comes into play if:

    • We want to support real-world OAuth2/OIDC login flows (e.g., SSO, GitHub, Google) (for external customers, not for internal Tigase usage)
    • We need fine-grained RBAC/identity federation for external consumers or tiers

    For Tygrys itself, this is strictly optional — we don’t plan to force its adoption or bundle it into the default Tygrys deployment.

    Memory and CPU Footprint of Keycloak (Quarkus-based)

    The newer versions of Keycloak (since 17.x) run on Quarkus, which significantly reduces their memory and CPU usage compared to the older WildFly versions. Based on tests in our dev clusters:

    • Idle memory usage: ~280–350 MB RSS
    • CPU usage: Virtually 0 when idle; minimal spikes on login or token validation
    • Startup time: ~2–3 seconds cold start with container warmup

    This footprint is often smaller than a Spring Boot app or even a MySQL sidecar.

    For comparison: • Apache James can easily consume >500 MB RAM depending on configuration • MySQL (with default tuning) often uses ~500–600 MB baseline • Keycloak with embedded DB + small realm + 2 clients = well under 400 MB RAM

    If needed, we can set explicit limits in the Helm chart (e.g., 512Mi, 0.25 CPU) and Keycloak will run just fine.

    Impact on Tygrys Admin UI

    You’re right that if we choose to expose Sztab’s Keycloak integration via the Tygrys Admin UI (e.g., to configure client secrets or realms), we’d need to account for that in the UI layer.

    But we don’t have to surface this in Tygrys unless there’s a clear need. For example: • Sztab-on-prem tiers can ship with a preconfigured Keycloak realm or use external identity providers via federation • Admin UI can defer to Sztab to expose a status endpoint that says “Auth provider: internal / external / Keycloak / none”

    This way, Tygrys doesn’t “own” Keycloak — it merely works with Sztab that optionally supports it.

    TL;DR • Keycloak is optional, and never forced into Tygrys • Newer versions are lightweight and resource-efficient • We’re keeping the Sztab security stack modular, so that simpler deployments (like the personal tier) can bypass Keycloak entirely • Happy to revisit this if we run into real-world resource pressure

    Let me know if you’d like a test deployment in the wedding planner cluster with memory/CPU limits so we can measure impact more concretely.

  • Artur Hefczyc commented 2 weeks ago

    Just my 2 cents.

    Minimal dependency or even no hard-dependency philosophy was something I adopted from the very first day working on Tigase XMPP Server. Even XML parser was implemented in-house. The goal was to have fully functional system without any dependencies. We tried to keep all dependencies optional. And this was result of my past bad experience with dependent libraries, versions, compatibility or even relationship with developers who refused to cooperate to fix or improve stuff.

    Another reason was that I wanted to have full control over the code, over licensing, changes we make and directions we want to go.

    I know this is not always realistic or financially feasible to implement all in-house. We do not have such resources, even if our knowledge and skills make it possible.

    So, keep dependencies to minimum, optional if possible and pay attention to licenses. LGPL, BSD, Apache are all OK. Basically, any open source license which allows us to package it and sell with our own software is OK.

  • rk@tigase.net commented 2 weeks ago

    I launched a test instance of Keycloak (Quarkus image) with minimal settings and email disabled and I get a POD at idle with near 0 CPU usage and 497MB. This can be reduced with resource constraints:

    ## My Test Keycloak values.yml: keycloak-test-pod.yml
    keycloak:
      username: admin
      password: admin123
    
      persistence:
        deployPostgres: false
    
      database:
        host: host.docker.internal
        port: 5433
        user: keycloak
        password: keycloak123
        database: keycloak
    
    postgresql:
      enabled: false
    
     % helm install my-keycloak codecentric/keycloak \  
      --namespace keycloak-test \
      --create-namespace \
      -f keycloak-test-pod.yml
     % kubectl get pods --namespace keycloak-test 
     NAME            READY   STATUS    RESTARTS   AGE
     my-keycloak-0   1/1     Running   0          9m46s
     % kubectl top pod -n keycloak-test           
     NAME            CPU(cores)   MEMORY(bytes)   
     my-keycloak-0   12m          485Mi           
     % 
    
  • rk@tigase.net commented 2 weeks ago

    Alternatives to Keycloak We can use a feather-light OIDC provider Dex (https://github.com/dexidp/dex) which is written Go. This is a minimal, stateless OIDC gateway with pluggable connectors (LDAP, GitHub, etc.)

    Or Ory Hydra - also implemented in Go - an embedded high-performance OAuth2/OIDC provider. But this is headless - not for us I think.

    Important: Again, do note that we will not be running this for Tigase internally. This is only an advertised feature of the system for open source users. Open source adopters look for enterprise functions.

  • Wojciech Kapcia (Tigase) commented 2 weeks ago

    Important: Again, do note that we will not be running this for Tigase internally. This is only an advertised feature of the system for open source users. Open source adopters look for enterprise functions.

    Should we be concerned with this (implement it) for the MVC? Considering that Sztab will be mostly used/focused as a issue management system within Tygrys, where everything will (should) use LDAP so we would have central point of authentication management?

  • rk@tigase.net commented 2 weeks ago

    If the question is "Is OIDC worth it for MVP?", consider this:

    • The cost of implementing JWT-based authentication has been modest — roughly 7 hours of well-contained effort (security configuration, test override beans, and sample JWT parsing via Keycloak claims).
    • I deliberately implemented the broader, long-term solution, and then scoped it to match MVP constraints (e.g., OIDC is optional and does not block LDAP or internal auth strategies).
    • Implementing narrowly for only the immediate use case (LDAP-only) would, ironically, increase long-term cost — it would make introducing OAuth2 later harder, risk more code churn, and require more retesting.
    • Also, this aligns with Artur’s vision of Sztab as a general-purpose OSS offering, not just an internal Tygrys tool. Open source adopters expect enterprise capabilities such as OIDC-capable login.

    To summarize: We’re not adding overhead for MVP — we’re preventing future rewrite costs by shaping the function more broadly from the start, without bloating the current delivery. If the time investment still feels disproportionate to MVP scope, I’m happy to absorb the cost of this work outside the project budget. This is an investment in future-proofing the platform.

    Here is the remaining effort for OAuth2/OIDC (with Keycloak integration only and with demo of GitHub authentication through OIDC):

    OIDC Login Options for Sztab

    OIDC Login via Keycloak Only

    Estimated Time: ~3.5 to 4 hours

    TaskTimeNotes
    Configure Spring Security for OAuth2 Login0.5hUse oauth2Login() in your SecurityConfig
    Add Keycloak client config to application.yml0.5hIncludes issuer-uri, client-id, redirect-uri
    Test login flow (redirect, callback, session)1.5hManual browser test + controller access
    Optional: Map roles via Keycloak claims0.5hMap realm roles to Spring authorities
    Document usage and sample Keycloak setup0.5–1hAdd guidance in README or .md file

    OIDC Login via GitHub (External IdP)

    Estimated Time: ~6 to 7 hours

    TaskTimeNotes
    Register GitHub OAuth app0.5–1hGet client ID, secret, set redirect URI
    Configure Spring Boot with GitHub client details0.5hDefine spring.security.oauth2.client.registration.github
    Update SecurityConfig with oauth2Login()0.5hSimilar setup as Keycloak
    Manual testing of login flow2hTest login, scope consent, callback
    Handle GitHub token and user attributes1–1.5hGitHub returns limited claims (e.g., no roles)
    Optional: Post-login authority mapping0.5hMay be skipped for MVP
    Documentation (for future devs)1hShow GitHub App setup + callback URI

    Summary

    ApproachTimeProsCons
    Keycloak~4 hrsSelf-contained, rich claims (roles/groups), extensibleAdds local Keycloak dependency
    GitHub~6–7 hrsEasy for developers, no extra serverNo role mapping, limited claims, GitHub app setup time
issue 1 of 1
Type
New Feature
Priority
Normal
Assignee
Version
1
Sprints
n/a
Customer
n/a
Issue Votes (0)
Watchers (4)
Reference
SZ-13
Please wait...
Page is in error, reload to recover