Basic Auth fails with HTTP 401 due to missing PasswordEncoder wiring (SZ-20)
rk@tigase.net opened 4 weeks ago

Problem

Sztab backend (v1.2) fails to authenticate the admin user via HTTP Basic Auth when running via Docker Compose, even though:

  • The database contains the correct BCrypt hash for the admin password.
  • The PasswordEncoder bean is correctly declared in BasicAuthSecurityConfig.

Root Cause

The JdbcUserDetailsManager bean is not explicitly wired to the PasswordEncoder, so Spring Security may use the default (noop) encoder during authentication, leading to password mismatch.

Logs

curl -v -u admin:admin123 http://localhost:8080/api/users
...
< HTTP/1.1 401

Workaround

No temporary workaround unless Spring autowires correctly via global context — behavior is unpredictable and environment-dependent.

Fix Plan

Inject the PasswordEncoder into JdbcUserDetailsManager explicitly in BasicAuthSecurityConfig:

@Bean
public JdbcUserDetailsManager userDetailsService(final DataSource dataSource,
                                                 final PasswordEncoder passwordEncoder) {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    manager.setPasswordEncoder(passwordEncoder);
    return manager;
}

Impact

Only affects Basic Auth login for users stored in the DB (e.g., admin). All other functionality remains unaffected.

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

    Estimated Time: 1h30m
    Actual Time Logged: TBD (after fix)

  • rk@tigase.net commented 4 weeks ago

    Work Log for SZ-20: Enable Basic Auth with JDBC-backed UserDetailsService

    Timeline

    • 2025-10-22: Issue created to address 401 Unauthorized errors despite correct credentials.
    • 2025-10-23:
      • Implemented CustomUserDetailsService backed by Spring Data JPA.
      • Updated BasicAuthSecurityConfig to use JDBC authentication and PasswordEncoder.
      • Added role-based access using @PreAuthorize and verified /api/users requires ROLE_ADMIN.
      • Verified login behavior via curl -u admin@sztab.local:admin123 on /api/users.
    • 2025-10-24:
      • Verified account creation and role assignment via POST /api/users.
      • Diagnosed and resolved TransientObjectException by passing role id in payload instead of just name.
      • Used curl to list roles and confirm IDs.
      • Added MarkdownViewController and /docs/index.html as static endpoint for testing public access.
      • Implemented and verified unit tests for CustomUserDetailsService.
      • Merged branch bugfix/sz20-basic-auth-yields-401 to wolnosc.

    Technical Changes

    • Created CustomUserDetailsService.java to load users and roles from database.
    • Updated Spring Security config (BasicAuthSecurityConfig.java) to:
      • Use UserDetailsService
      • Encode passwords using BCryptPasswordEncoder
      • Permit actuator endpoints and static docs without authentication
    • Cleaned up application.yml to use properties appropriate for JDBC auth
    • Verified via curl that:
      • Basic Auth protects /api/** endpoints
      • Role-based restrictions are enforced
      • New users can be created with valid credentials
    • Ensured new controller (/docs/**) is public

    Resolution

    Basic Authentication is now enabled and functional using a JDBC-backed UserDetailsService.
    Test coverage added for the service, and curl calls verified successful user creation and protection of endpoints.
    Branch bugfix/sz20-basic-auth-yields-401 merged to wolnosc.

    No version bump needed as this does not introduce any new externally visible features — internal correctness fix only.

  • rk@tigase.net commented 4 weeks ago

    Resolved.

    Final implementation uses a JDBC-backed UserDetailsService and fully verifies Basic Auth with curl-based user creation, login, and role retrieval.

    I originally estimated 2 hours but ended up spending ~7 hours, including time on a now-suspended UT approach. This helped clarify some Spring Security profile behavior, and I’ll factor this into future estimates.

    Changes merged into wolnosc.

  • rk@tigase.net changed state to 'Closed' 4 weeks ago
    Previous Value Current Value
    In Progress
    Closed
  • rk@tigase.net referenced from other issue 4 weeks ago
  • rk@tigase.net commented 3 weeks ago

    Engg-notes:

    Hibernate Authentication Issue: Troubleshooting Recap

    Problem

    Despite having a user in the database (admin@sztab.local), all REST API calls with basic auth were returning 401 Unauthorized.

    Symptoms

    • curl -u admin@sztab.local:admin ... → always failed with 401.
    • Spring logs showed:
      BadCredentialsException: Bad credentials
      
    • DB query revealed the user password was stored in BCrypt form ($2a$10$...), but the login password was admin (plaintext).
    • You also tried {noop}password, but that didn’t work either.

    Diagnosis

    Spring Security uses DaoAuthenticationProvider, which hashes the input password and compares it to the stored hash in user.password.

    This is the correct behavior — but:

    • You had manually inserted or updated users in the DB
    • The password was either:
      • Stored as {noop}password (only works with NoOpPasswordEncoder)
      • Or out-of-sync with what Spring expected
      • Or improperly hashed

    So Spring couldn't match the credentials, and failed the authentication attempt.


    Resolution

    You generated a BCrypt hash for the password admin using Python:

    docker run --rm python:3-alpine \
      sh -c "pip install bcrypt > /dev/null && python -c 'import bcrypt; print(bcrypt.hashpw(b\"admin\", bcrypt.gensalt()).decode())'"
    

    This output a bcrypt hash like:

    $2b$12$4/Kl9lOOUO1kLODkBUcXzeaQqEBFynd2PVhZefbQH3vvHK39sUn.W
    

    Then you updated the user table directly:

    UPDATE "user"
    SET password = '$2b$12$4/Kl9lOOUO1kLODkBUcXzeaQqEBFynd2PVhZefbQH3vvHK39sUn.W'
    WHERE username = 'admin';
    

    And restarted the backend app:

    docker restart sztab-backend
    

    Then confirmed it worked:

    curl -u admin@sztab.local:admin http://localhost:8181/api/users
    

    Response: HTTP 200 + JSON user list


    Takeaways

    • Always use BCrypt-hashed passwords (never {noop} unless explicitly supported)
    • Don’t insert plaintext passwords directly into the DB unless it's a test setup that uses NoOpPasswordEncoder
    • If changing password manually → restart the backend
    • Spring Security defaults to BCrypt, which is good (don’t override it casually)

    Tip

    To validate a password hash, you can also do it in a quick script:

    import bcrypt
    
    password = b"admin"
    hashed = b"$2b$12$...."
    print(bcrypt.checkpw(password, hashed))  # True or False
    
issue 1 of 1
Type
Bug
Priority
Normal
Assignee
Version
1.0
Sprints
n/a
Customer
n/a
Issue Votes (0)
Watchers (3)
Reference
SZ-20
Please wait...
Page is in error, reload to recover