DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • Never Use Credentials in a CI/CD Pipeline Again
  • How To Integrate Microsoft Team With Cypress Cloud
  • Transactional Outbox Patterns Step by Step With Spring and Kotlin
  • Seven Steps To Deploy Kedro Pipelines on Amazon EMR

Trending

  • Never Use Credentials in a CI/CD Pipeline Again
  • How To Integrate Microsoft Team With Cypress Cloud
  • Transactional Outbox Patterns Step by Step With Spring and Kotlin
  • Seven Steps To Deploy Kedro Pipelines on Amazon EMR
  1. DZone
  2. Data Engineering
  3. Databases
  4. Jakarta Security and REST on Cloud: Part 4 Combining JWT With OAuth2

Jakarta Security and REST on Cloud: Part 4 Combining JWT With OAuth2

Otavio Santana user avatar by
Otavio Santana
CORE ·
Jul. 21, 20 · Tutorial
Like (4)
Save
Tweet
Share
5.78K Views

Join the DZone community and get the full member experience.

Join For Free

OAuth2 is undoubtedly one of the most famous security protocols today. One of its advantages is the non-exposure of sensitive information, such as user and password, consistently, as done by the BASIC mechanism. However, there is an increase in its complexity, especially when we talk about exchanging tokens, which do not have much use since it does not contain any information. However, we can make them have a little responsibility, such as transporting information safely. This post will talk a little about how to integrate OAuth2 with JWTs.

In part 3 of this series, we talked about the Oauth2 mechanism and the costs and benefits involving complexity and the possibility of not overexposing login and password data. One of the outstanding characteristics of this mechanism is found in the security communication from the token. Until then, it has only one use: Reference. This pointer works like a link. However, it does not have the information itself. It works as an Oauth2 mechanism. We send a token, which in turn is checked for existence in the database so that the user's authentication information and respective credentials are searched. That is, the user's entire state is in the bank.

This approach can cause performance problems, since we need to verify the authenticity of such a token, being necessary to search the information in the database in every interaction. That is, if a service needs to integrate with four others within the same authentication and authorization mechanism, it will need to seek the same state of the user all four times.

A new strategy would be to use the token in a complete way instead of just as a pointer. This means that if we were able to store user information with the symbol itself, we would have a performance gain. This is possible thanks to JSON Web Tokens, an open industry standard for representing secure communication between parties. With JWTs, we have the option to sign and/or encrypt the information we want; in this case, it would be the user and the roles to be used. The focus of the article is not to talk about JWT, but about the integration with Oauth2. If the reader wants to know more information about the subject, there is a very cool Handbook about JWTs.

Let's start by getting hands-on using the design of part 3, so we don't have to start from scratch. The first step will be to add the JWT to the dependency. This library will be responsible for reading and writing the JWT.

XML
xxxxxxxxxx
1
 
1
<dependency>
2
    <groupId>com.auth0</groupId>
3
    <artifactId>java-jwt</artifactId>
4
    <version>3.10.3</version>
5
</dependency>
6

          


With the dependencies defined, the next step will be to modify the code itself. In entities, AccessToken will be modified, as we will change the ID so that it can receive the JWT as String, in addition to storing the secret. This secret will be unique and randomly generated for each entity and will be responsible for confirming the signature and for verifying the authenticity of the JWT. It is worth noting that the signature is responsible for verifying the authenticity of the JWT and for confirming that it has not been modified, that is, the signature does not mean encryption. For more information on JWT and cryptography, we can access the link for JWE.

Java
xxxxxxxxxx
1
17
 
1
@Entity
2
@JsonbVisibility(FieldPropertyVisibilityStrategy.class)
3
public class AccessToken {
4

          
5
    static final String PREFIX = "access_token:";
6

          
7
    @Id
8
    private String id;
9
    @JsonbProperty
10
    private String user;
11
    @JsonbProperty
12
    private String token;
13
    @JsonbProperty
14
    private String jwtSecret;
15
    //...
16
}
17

          


To facilitate the reading and manipulation of the JWT, the UserJWT will be created. This class will generate the JWT in text format. It is interesting to note that there is a check on creation in the UserJWT factory method. This verification is done by the signature, previously explained, and also by the expiration date.

Java
xxxxxxxxxx
1
73
 
1
import com.auth0.jwt.JWT;
2
import com.auth0.jwt.JWTVerifier;
3
import com.auth0.jwt.algorithms.Algorithm;
4
import com.auth0.jwt.exceptions.JWTVerificationException;
5
import com.auth0.jwt.interfaces.Claim;
6
import com.auth0.jwt.interfaces.DecodedJWT;
7
import sh.platform.sample.security.User;
8

          
9
import java.time.Duration;
10
import java.time.LocalDateTime;
11
import java.time.ZoneOffset;
12
import java.util.ArrayList;
13
import java.util.Collections;
14
import java.util.Date;
15
import java.util.Optional;
16
import java.util.Set;
17
import java.util.logging.Level;
18
import java.util.logging.Logger;
19
import java.util.stream.Collectors;
20

          
21
class UserJWT {
22

          
23
    private static final Logger LOGGER = Logger.getLogger(UserJWT.class.getName());
24
    private static final String ISSUER = "jakarta";
25
    private static final String ROLES = "roles";
26

          
27
    private final String user;
28

          
29
    private final Set<String> roles;
30

          
31
    UserJWT(String user, Set<String> roles) {
32
        this.user = user;
33
        this.roles = roles;
34
    }
35

          
36
    public String getUser() {
37
        return user;
38
    }
39

          
40
    public Set<String> getRoles() {
41
        if (roles == null) {
42
            return Collections.emptySet();
43
        }
44
        return roles;
45
    }
46

          
47
    static String createToken(User user, Token token, Duration duration) {
48
        final LocalDateTime expire = LocalDateTime.now(ZoneOffset.UTC).plusMinutes(duration.toMinutes());
49
        Algorithm algorithm = Algorithm.HMAC256(token.get());
50
        return JWT.create()
51
                .withJWTId(user.getName())
52
                .withIssuer(ISSUER)
53
                .withExpiresAt(Date.from(expire.atZone(ZoneOffset.UTC).toInstant()))
54
                .withClaim(ROLES, new ArrayList<>(user.getRoles()))
55
                .sign(algorithm);
56

          
57
    }
58

          
59
    static Optional<UserJWT> parse(String jwtText, Token token) {
60
        Algorithm algorithm = Algorithm.HMAC256(token.get());
61
        try {
62
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
63
            final DecodedJWT jwt = verifier.verify(jwtText);
64
            final Claim roles = jwt.getClaim(ROLES);
65
            return Optional.of(new UserJWT(jwt.getId(),
66
                    roles.asList(String.class).stream().collect(Collectors.toUnmodifiableSet())));
67
        } catch (JWTVerificationException exp) {
68
            LOGGER.log(Level.WARNING, "There is an error to load the JWT token", exp);
69
            return Optional.empty();
70
        }
71
    }
72
}
73

          


Once the modeling has modified, the next step is to change the OAuth2 service. The interesting point is that in addition to the existing TTL, the JWT also checks the data expiration. That is, there is a double check of data consistency in this signal.

Java
xxxxxxxxxx
1
83
 
1
import jakarta.nosql.mapping.keyvalue.KeyValueTemplate;
2
import org.eclipse.microprofile.config.inject.ConfigProperty;
3
import sh.platform.sample.security.SecurityService;
4
import sh.platform.sample.security.User;
5
import sh.platform.sample.security.UserNotAuthorizedException;
6

          
7
import javax.enterprise.context.ApplicationScoped;
8
import javax.inject.Inject;
9
import javax.validation.ConstraintViolation;
10
import javax.validation.ConstraintViolationException;
11
import javax.validation.Validator;
12
import java.time.Duration;
13
import java.util.Arrays;
14
import java.util.Set;
15

          
16
@ApplicationScoped
17
class Oauth2Service {
18

          
19
    static final int EXPIRE_IN = 3600;
20

          
21
    static final Duration EXPIRES = Duration.ofSeconds(EXPIRE_IN);
22

          
23
    @Inject
24
    private SecurityService securityService;
25

          
26
    @Inject
27
    @ConfigProperty(name = "keyvalue")
28
    private KeyValueTemplate template;
29

          
30
    @Inject
31
    private Validator validator;
32

          
33
    public Oauth2Response token(Oauth2Request request) {
34

          
35
        final Set<ConstraintViolation<Oauth2Request>> violations = validator.validate(request, Oauth2Request
36
                .GenerateToken.class);
37
        if (!violations.isEmpty()) {
38
            throw new ConstraintViolationException(violations);
39
        }
40

          
41
        final User user = securityService.findBy(request.getUsername(), request.getPassword());
42
        final UserToken userToken = template.get(request.getUsername(), UserToken.class)
43
                .orElse(new UserToken(user.getName()));
44

          
45
        final Token token = Token.generate();
46

          
47
        final String jwt = UserJWT.createToken(user, token, EXPIRES);
48
        AccessToken accessToken = new AccessToken(jwt, token, user.getName());
49
        RefreshToken refreshToken = new RefreshToken(userToken, jwt, user.getName());
50

          
51
        template.put(refreshToken, EXPIRES);
52
        template.put(Arrays.asList(userToken, accessToken));
53

          
54
        final Oauth2Response response = Oauth2Response.of(accessToken, refreshToken, EXPIRE_IN);
55
        return response;
56
    }
57

          
58
    public Oauth2Response refreshToken(Oauth2Request request) {
59
        final Set<ConstraintViolation<Oauth2Request>> violations = validator.validate(request, Oauth2Request
60
                .RefreshToken.class);
61
        if (!violations.isEmpty()) {
62
            throw new ConstraintViolationException(violations);
63
        }
64

          
65
        RefreshToken refreshToken = template.get(RefreshToken.PREFIX + request.getRefreshToken(), RefreshToken.class)
66
                .orElseThrow(() -> new UserNotAuthorizedException("Invalid Token"));
67

          
68
        final UserToken userToken = template.get(refreshToken.getUser(), UserToken.class)
69
                .orElse(new UserToken(refreshToken.getUser()));
70

          
71
        final User user = securityService.findBy(refreshToken.getUser());
72

          
73
        final Token token = Token.generate();
74
        final String jwt = UserJWT.createToken(user, token, EXPIRES);
75
        AccessToken accessToken = new AccessToken(jwt, token, refreshToken.getUser());
76
        refreshToken.update(accessToken, userToken, template);
77
        template.put(accessToken, EXPIRES);
78
        final Oauth2Response response = Oauth2Response.of(accessToken, refreshToken, EXPIRE_IN);
79
        return response;
80
    }
81

          
82
}
83

          


The last step will be to change the mechanism. In general, user information will not be retrieved from the database, but from the JWT itself.

Java
xxxxxxxxxx
1
52
 
1
import jakarta.nosql.mapping.keyvalue.KeyValueTemplate;
2
import org.eclipse.microprofile.config.inject.ConfigProperty;
3

          
4
import javax.enterprise.context.ApplicationScoped;
5
import javax.inject.Inject;
6
import javax.security.enterprise.AuthenticationStatus;
7
import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
8
import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
9
import javax.servlet.http.HttpServletRequest;
10
import javax.servlet.http.HttpServletResponse;
11
import java.util.Optional;
12
import java.util.regex.Matcher;
13
import java.util.regex.Pattern;
14

          
15
@ApplicationScoped
16
public class Oauth2Authentication implements HttpAuthenticationMechanism {
17

          
18
    private static final Pattern CHALLENGE_PATTERN
19
            = Pattern.compile("^Bearer *([^ ]+) *$", Pattern.CASE_INSENSITIVE);
20

          
21
    @Inject
22
    @ConfigProperty(name = "keyvalue")
23
    private KeyValueTemplate template;
24

          
25
    @Override
26
    public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
27
                                                HttpMessageContext httpMessageContext) {
28

          
29

          
30
        final String authorization = request.getHeader("Authorization");
31

          
32
        Matcher matcher = CHALLENGE_PATTERN.matcher(Optional.ofNullable(authorization).orElse(""));
33
        if (!matcher.matches()) {
34
            return httpMessageContext.doNothing();
35
        }
36
        final String token = matcher.group(1);
37
        final Optional<AccessToken> optional = template.get(AccessToken.PREFIX + token, AccessToken.class);
38

          
39
        if (!optional.isPresent()) {
40
            return httpMessageContext.responseUnauthorized();
41
        }
42
        final AccessToken accessToken = optional.get();
43
        final Optional<UserJWT> optionalUserJWT = UserJWT.parse(accessToken.getToken(), accessToken.getJwtSecretAsToken());
44
        if (optionalUserJWT.isPresent()) {
45
            final UserJWT userJWT = optionalUserJWT.get();
46
            return httpMessageContext.notifyContainerAboutLogin(userJWT.getUser(), userJWT.getRoles());
47
        } else {
48
            return httpMessageContext.responseUnauthorized();
49
        }
50
    }
51
}
52

          


Moving to the Cloud

As there was no change in services or structure of the containers, the structure and configuration for sending the application to the cloud with Platform.sh will be maintained in the same way.

There, the mechanism was refactored not to use the information in the database, thus reducing the number of requests. However, we have a big problem with data consistency, since there is still the possibility of a change in the database that will be reflected only when the token expires. The user would need to update their tokens with the refresh token. There is a strategy to work with this, which would be to launch events to make the current token unfeasible, forcing the user to do the refresh token process early.

The critical point is that there is always a disadvantage between data consistency and availability, and it is up to the architect to understand each case and choose the best strategy. The main objective of this series of articles is to know how the security API works. However, every line of code we write is code that will need to be maintained, so using an existing solution may be the best option. There are security solutions that already exist and are worth taking a look at, like Okta and Keycloak.

As always, the code example is from GitHub.

JWT (JSON Web Token) security authentication REST Web Protocols Cloud Database

Opinions expressed by DZone contributors are their own.

Trending

  • Never Use Credentials in a CI/CD Pipeline Again
  • How To Integrate Microsoft Team With Cypress Cloud
  • Transactional Outbox Patterns Step by Step With Spring and Kotlin
  • Seven Steps To Deploy Kedro Pipelines on Amazon EMR

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: