PAT Authentication for REST API (SZ-117)
rk@tigase.net opened 2 weeks ago

Problem

I woke up to this problem today when discussing the Bot testing approach over email with Artur. PATs only work for git operations (via Caddy forward_auth). REST API consumers — CI/CD pipelines, third-party integrations, authorized bots — have no way to authenticate programmatically without simulating browser session login, which is fragile and stateful.

Expected Behavior

Any REST API call with Authorization: Bearer <token> should be validated against PersonalAccessTokenRepository and granted access with the token owner's identity and roles, identical to a session-authenticated user.

Current Behavior

Spring Security ignores the Authorization: Bearer header on REST endpoints. Only session cookies (JSESSIONID) are recognized. PATs are validated by Caddy only for /git/* routes.

Fix

Implement a PATAuthenticationFilter in Spring Security:

  1. Extract Authorization: Bearer <token> from the request header
  2. Look up the token in PersonalAccessTokenRepository
  3. Validate — not expired, not revoked
  4. Set the SecurityContext with the token owner's identity and roles
  5. Wire into SessionAuthSecurityConfig before UsernamePasswordAuthenticationFilter

Estimate

2-3h

Worklog

StepTaskEst
1Create PATAuthenticationFilter extending OncePerRequestFilter1h
2Wire filter into SessionAuthSecurityConfig30m
3Test with curl using a real PAT against staging30m
4Update stress test script to use PAT instead of session cookie30m

Notes

  • Industry standard: browser uses session cookie, everything else uses PAT as Bearer token
  • GitHub, GitLab, Jira all follow this pattern
  • This is a prerequisite for authorized bot access (SZ-73 Layer 4)
  • PersonalAccessTokenRepository and PATHashService already exist — filter is the missing piece
  • rk@tigase.net commented 1 week ago
    rksuma@Ramakrishnans-MacBook-Pro sztab % git checkout -b feature/SZ-117-PAT-Authentication-for-REST-API
    Switched to a new branch 'feature/SZ-117-PAT-Authentication-for-REST-API'
    rksuma@Ramakrishnans-MacBook-Pro sztab % 
    
    
  • rk@tigase.net changed state to 'In Progress' 1 week ago
    Previous Value Current Value
    Open
    In Progress
  • rk@tigase.net commented 1 week ago

    Implementation Overview:

    I will implement PAT authentication of REST API by using a dedicated Spring Security Filter which parses the bearer token and if present, validates it and create security context.

    Details:

    PATAuthenticationFilter extends OncePerRequestFilter, extracts the Bearer token, validates it against PersonalAccessTokenRepository (not expired, not revoked), and populates the SecurityContext with the token owner's identity and roles. Wired into SessionAuthSecurityConfig before UsernamePasswordAuthenticationFilter so it runs first without disturbing session-based auth for browser clients.

    Unit Test Cases:

    • Happy path — valid global PAT, SecurityContext populated with the token owner's identity
    • Non-existent PAT — resolveToken returns empty, SecurityContext remains null, 403 from downstream
    • Revoked PAT — same result as case 2, resolveToken returns empty because findActiveByPrefix excludes revoked tokens at the query level
    • Project-scoped PAT, wrong project — PAT is valid but scoped to project A, request targets project B, filter halts the chain and returns 403 directly
    • Project-scoped PAT, correct project — same PAT as case 4 but request targets project A, SecurityContext populated, chain continues normally

    Please note: In Spring Security, once authentication is complete, everything downstream only cares about if Authentication token can be retrieved from SecurityCOntext as in

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    

    It does not care whether it came from Form login, OAuth2, JWT, API Key, PA, Kerberos or LDAP.

    The following is the flow of Sztab spring security filters and where PATAuthenticationFilter in this flow:

    Screenshot 2026-03-21 at 3.54.30 PM.pngScreenshot 2026-03-21 at 3.54.30 PM

  • rk@tigase.net commented 1 week ago
    rksuma@Ramakrishnans-MacBook-Pro backend %  mvn clean test
    //...
    [WARNING] Tests run: 310, Failures: 0, Errors: 0, Skipped: 57
    [INFO] 
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  11.804 s
    [INFO] Finished at: 2026-03-21T13:00:49-07:00
    [INFO] ------------------------------------------------------------------------
    rksuma@Ramakrishnans-MacBook-Pro backend % git add -A; git status
    On branch feature/SZ-117-PAT-Authentication-for-REST-API
    Your branch is up to date with 'origin/feature/SZ-117-PAT-Authentication-for-REST-API'.
    
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
    	modified:   src/main/java/com/sztab/model/PersonalAccessToken.java
    	modified:   src/main/java/com/sztab/repository/PersonalAccessTokenRepository.java
    	modified:   src/main/java/com/sztab/security/config/SessionAuthSecurityConfig.java
    	new file:   src/main/java/com/sztab/security/entity/PATPrincipal.java
    	modified:   src/main/java/com/sztab/security/filter/PATAuthenticationFilter.java
    	new file:   src/main/resources/db/migration/V16__drop_scopes_column_from_pat_entity.sql
    	modified:   src/test/java/com/sztab/security/filter/PATAuthenticationFilterTest.java
    
    rksuma@Ramakrishnans-MacBook-Pro backend % git commit
    [feature/SZ-117-PAT-Authentication-for-REST-API c8d111f] Introduce PATAuthenticationFilter to enable stateless Bearer token authentication for REST endpoints.
     7 files changed, 133 insertions(+), 103 deletions(-)
     create mode 100644 backend/src/main/java/com/sztab/security/entity/PATPrincipal.java
     create mode 100644 backend/src/main/resources/db/migration/V16__drop_scopes_column_from_pat_entity.sql
    rksuma@Ramakrishnans-MacBook-Pro backend % git push
    Enumerating objects: 53, done.
    Counting objects: 100% (53/53), done.
    Delta compression using up to 12 threads
    Compressing objects: 100% (24/24), done.
    Writing objects: 100% (30/30), 4.87 KiB | 4.87 MiB/s, done.
    Total 30 (delta 14), reused 0 (delta 0), pack-reused 0 (from 0)
    remote:  
    remote: Create a pull request for 'feature/SZ-117-PAT-Authentication-for-REST-API' by visiting:
    remote:     https://tigase.dev/sztab/~pulls/new?target=1325:wolnosc&source=1325:feature/SZ-117-PAT-Authentication-for-REST-API
    remote:  
    To https://tigase.dev/sztab.git
       bc15db9..c8d111f  feature/SZ-117-PAT-Authentication-for-REST-API -> feature/SZ-117-PAT-Authentication-for-REST-API
    rksuma@Ramakrishnans-MacBook-Pro backend % git checkout wolnosc
    Switched to branch 'wolnosc'
    Your branch is up to date with 'origin/wolnosc'.
    rksuma@Ramakrishnans-MacBook-Pro backend % git pull origin wolnosc
    From https://tigase.dev/sztab
     * branch            wolnosc    -> FETCH_HEAD
    Already up to date.
    rksuma@Ramakrishnans-MacBook-Pro backend % git merge --ff-only feature/SZ-117-PAT-Authentication-for-REST-API
    Updating 256c6b2..c8d111f
    Fast-forward
     backend/src/main/java/com/sztab/model/PersonalAccessToken.java                      |   8 ++-
     backend/src/main/java/com/sztab/repository/PersonalAccessTokenRepository.java       |  27 +++++----
     backend/src/main/java/com/sztab/security/config/SessionAuthSecurityConfig.java      |  15 +++++
     backend/src/main/java/com/sztab/security/entity/PATPrincipal.java                   |  17 ++++++
     backend/src/main/java/com/sztab/security/filter/PATAuthenticationFilter.java        |  74 ++++++++++++++++++++++++
     backend/src/main/resources/db/migration/V16__drop_scopes_column_from_pat_entity.sql |   3 +
     backend/src/test/java/com/sztab/security/filter/PATAuthenticationFilterTest.java    | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     7 files changed, 350 insertions(+), 12 deletions(-)
     create mode 100644 backend/src/main/java/com/sztab/security/entity/PATPrincipal.java
     create mode 100644 backend/src/main/java/com/sztab/security/filter/PATAuthenticationFilter.java
     create mode 100644 backend/src/main/resources/db/migration/V16__drop_scopes_column_from_pat_entity.sql
     create mode 100644 backend/src/test/java/com/sztab/security/filter/PATAuthenticationFilterTest.java
    rksuma@Ramakrishnans-MacBook-Pro backend % git push origin wolnosc
    Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
    To https://tigase.dev/sztab.git
       256c6b2..c8d111f  wolnosc -> wolnosc
    rksuma@Ramakrishnans-MacBook-Pro backend % git branch -d feature/SZ-117-PAT-Authentication-for-REST-API
    Deleted branch feature/SZ-117-PAT-Authentication-for-REST-API (was c8d111f).
    rksuma@Ramakrishnans-MacBook-Pro backend % git push origin --delete feature/SZ-117-PAT-Authentication-for-REST-API
    remote:  
    remote: Create a pull request for 'feature/SZ-117-PAT-Authentication-for-REST-API' by visiting:
    remote:     https://tigase.dev/sztab/~pulls/new?target=1325:wolnosc&source=1325:feature/SZ-117-PAT-Authentication-for-REST-API
    remote:  
    To https://tigase.dev/sztab.git
     - [deleted]         feature/SZ-117-PAT-Authentication-for-REST-API
    rksuma@Ramakrishnans-MacBook-Pro backend % 
    
    
  • rk@tigase.net changed state to 'Closed' 1 week ago
    Previous Value Current Value
    In Progress
    Closed
issue 1 of 1
Type
New Feature
Priority
Normal
Assignee
Version
none
Sprints
n/a
Customer
n/a
Issue Votes (0)
Watchers (3)
Reference
SZ-117
Please wait...
Page is in error, reload to recover