Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Spring boot Unique index or primary key violation when using jwt to authenticate, when multiple authentication requests in a short time

I am using jwt to authenticate in my spring boot app. These are methods in AuthenticationService:

public AuthenticationResponse authenticate(AuthenticationRequest request) {
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.getEmail(),
                        request.getPassword()
                )
        );
        User user = userRepository.findByEmail(request.getEmail()).orElseThrow(UserNotFoundException::new);
        String jwtToken = jwtService.generateToken(user);
        String refreshToken = jwtService.generateRefreshToken(user);
        revokeAllUserTokens(user);
        saveUserToken(user, jwtToken);
        return AuthenticationResponse.builder()
                .accessToken(jwtToken)
                .refreshToken(refreshToken)
                .build();
    }

private void revokeAllUserTokens(User user) {
        List<Token> validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
        if(validUserTokens.isEmpty())
            return;
        validUserTokens.forEach(token -> {
            token.setExpired(true);
            token.setRevoked(true);
        });
        tokenRepository.saveAll(validUserTokens);
    }

private void saveUserToken(User user, String jwtToken) {
        Token token = Token.builder()
                .user(user)
                .token(jwtToken)
                .tokenType(TokenType.BEARER)
                .expired(false)
                .revoked(false)
                .build();
        tokenRepository.save(token);
    }

and in JwtService:

public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
        return buildToken(extraClaims, userDetails, jwtConfig.getExpiration());
    }

    public String generateRefreshToken(UserDetails userDetails) {
        return buildToken(new HashMap<>(), userDetails, jwtConfig.getRefreshTokenExpiration());
    }

and Token inside database:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "jwt_tokens")
public class Token {

    @Id
    @GeneratedValue
    public Long id;

    @Column(name = "token", unique = true)
    public String token;

    @Enumerated(EnumType.STRING)
    public TokenType tokenType = TokenType.BEARER;

    public boolean revoked;
    public boolean expired;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    public User user;

}

rest of the implementation is rather self explanatory.

It works fine, but when there are multiple authentication request (when I rapidly send multiple requests), there is 500 status response, and an exception is thrown (method authenticate in AuthenticationService) is called:

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_4 ON PUBLIC.JWT_TOKENS(TOKEN NULLS FIRST) VALUES ( /* 7 */ 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYWx5enNAZW1haWwuY29tIiwiaWF0IjoxNjkwMjE4NDU3LCJleHAiOjE2OTAzMDQ4NTd9.8aePz6jl3JPvsnDFVpXxtzYfsxLN0ZqIUg0FsK_XDaE' )"; SQL statement:
insert into jwt_tokens (expired,revoked,token,token_type,user_id,id) values (?,?,?,?,?,?) [23505-214]

>Solution :

JWT tokens, by design, use UNIX Epoch time in seconds for iat (issued at) time.

I reckon a good hack would be to include additional information in extraClaims when generating JWT tokens, for example the current time in miliseconds. This doesn’t change the usage of tokens as they still get hashed.

In terms of your implementation, a possible change would be:

String jwtToken = jwtService.generateToken(Map.of("milis", new Date(System.currentTimeMillis())), user);
String refreshToken = jwtService.generateRefreshToken(Map.of("milis", new Date(System.currentTimeMillis())), user);
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading