[Micronaut] Try Cookie
Tags: java, micronaut
Published: 2024-03-18 16:56:30

## Setting cookies on redirect
First, I will set a cookie value on redirect.
I created two micronaut applications.

When a user access the application1, it redirects to application2 and set a cookie.

#### [Application1] HomeController.java
```java
package jp.masanori;

import java.util.Optional;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.cookie.Cookie;
import io.micronaut.http.cookie.Cookies;
import io.micronaut.http.cookie.SameSite;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.CookieValue;

@Controller("/")
public class HomeController {
	private final Logger logger;
	public HomeController() {
		this.logger = LoggerFactory.getLogger(HomeController.class);
	}
    @Get("/")
    public String index(@CookieValue("SESSION-VALUE") Optional<String> sessionValue,
            HttpRequest<String> req) {
        if (sessionValue.isPresent()) {
            logger.debug("OK: " + sessionValue.get());
        } else {
            logger.debug("No cookie");
        }
        Cookies c = req.getCookies();
        for (var v : c.getAll()) {
            logger.debug("--------Cookie---------");
            logger.debug("Name:" + v.getName());
            logger.debug("Value: " + v.getValue());
            logger.debug("Domain:" + v.getDomain());
            logger.debug("Path: " + v.getPath());
            logger.debug("MaxAge: " + v.getMaxAge());
            logger.debug("SameSite: " + v.getSameSite());
            logger.debug("HttpOnly: " + v.isHttpOnly());
            logger.debug("Secure: " + v.isSecure());
        }
        return "Hello World!";
    }
    @Get("/redirect")
    public MutableHttpResponse<Object> redirectToOutTasks() {
        URI location = URI.create("http://ourtasks.masanori.jp:8083/ourtasks/pages/cookie");
        
        Cookie ck = Cookie.of("SESSION-VALUE", "Hello Micronaut!");
        return HttpResponse.redirect(location)
            .cookie(ck);
    }
}
```

#### [Application2] PageController.java
```java
package jp.masanori.apps;

import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.CookieValue;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.views.View;

@Controller("/pages")
public class PageController {
    private final Logger logger;

    public PageController() {
        this.logger = LoggerFactory.getLogger(PageController.class);
    }

    @Produces(MediaType.TEXT_HTML)
    @Get("/cookie")
    @View("editTask")
    public HttpResponse<String> getCookieSample(@CookieValue("SESSION-VALUE") Optional<String> sessionValue) {
        if (sessionValue.isPresent()) {
            logger.debug("OK: " + sessionValue.get());
        } else {
            logger.debug("No cookie");
        }
        return HttpResponse.ok();
    }
}
```

### Set cookies into subdomains
By defaut, the application2 can't get the cookie value.
When I access sample.masanori.jp:8086(HomeController.index() of application1 is called),
I will get logs like below.
```
--------Cookie---------
Name:SESSION-VALUE
Value: Hello Micronaut!
Domain:null
Path: null
MaxAge: -9223372036854775808
SameSite: Optional.empty
HttpOnly: false
Secure: false
```

Because their domains are just added into the hosts file of Windows,
so if I change the application1 domain into "ourtasks.masanori.jp:8086", the application2 can get the cookie value.

#### hosts
```
...
127.0.0.1 ourtasks.masanori.jp
127.0.0.1 sample.masanori.jp
```

To share cookie values between their domains, I will add some options.

#### [Application1] HomeController.java
```java
...
	@Get("/redirect")
    public MutableHttpResponse<Object> redirectToOutTasks() {
        URI location = URI.create("http://ourtasks.masanori.jp:8083/ourtasks/pages/cookie");
        
        Cookie ck = Cookie.of("SESSION-VALUE", "Hello Micronaut!!");
        // To avoid accessing the cookie value from the client-side
        ck.httpOnly(true);
        // Share the cookie value on "masanori.jp" or "*.masanori.jp".
		// Both the application1 and application2 have to belong to these domains. 
        ck.domain("masanori.jp");
        ck.sameSite(SameSite.Strict);
        return HttpResponse.redirect(location)
            .cookie(ck);
    }
}
```

### Set cookies for Undertow
When I use Under tow as a web server, the above code could not set cookies.
(Although I can see the value on a web browser, but I could not get it on my application)
So I should add "Set-Cookie" into the HttpResponse headers.

#### build.gradle
```java
...
micronaut {
    runtime("undertow")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("jp.masanori.*")
    }
    aot {
...
    }
}
...
```

#### PageController.java
```java
...
    @Get("/jwt")
    public MutableHttpResponse<?> tryJwt(@CookieValue("JWT-VALUE") Optional<String> sessionValue) {
        if (sessionValue.isPresent()) {
            logger.info("OK: " + sessionValue.get());
            return HttpResponse.ok();
        } else {
            return HttpResponse.ok()
                    // I could not use "cookie()" for Undertow
                    /* .cookie(ck) */
                    .header("Set-Cookie", String.format("JWT-VALUE=%s;path=/;HttpOnly;", "Hello"));
        }
    }
...
```

* [RFC6265 - HTTP State Management Mechanism](https://datatracker.ietf.org/doc/html/rfc6265)
* [Using HTTP cookies - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
* [Set-Cookie - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)

## Set JWT to cookies
I will try setting JWT as a cookie value using java-jwt.

* [auth0/java-jwt - GitHub](https://github.com/auth0/java-jwt)

#### PageController.java
```java

import java.util.Date;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.CookieValue;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.views.View;
import io.micronaut.http.cookie.Cookies;

@Controller("/pages")
public class PageController {
...
    @Get("/jwt")
    public MutableHttpResponse<?> tryJwt(@CookieValue("JWT-VALUE") Optional<String> sessionValue) {
        if (sessionValue.isPresent()) {
            if (validateJwt(sessionValue)) {
                return HttpResponse.ok();
            } else {
                return HttpResponse.unauthorized();
            }
        } else {
            return HttpResponse.ok()
                    /* .cookie(ck) */
                    .header("Set-Cookie", String.format("JWT-VALUE=%s;path=/;HttpOnly;", generateJwt()));
        }
    }

    private String generateJwt() {
        try {
            Date expireTime = new Date();
            expireTime.setTime(expireTime.getTime() + 10000);

            Algorithm algorithm = Algorithm.HMAC256("secret");
            return JWT.create()
                    .withClaim("message", "hello")
                    .withIssuer("masanori")
                    .withExpiresAt(expireTime)
                    .sign(algorithm);
        } catch (JWTCreationException exception) {
            logger.error("JWT Error: ", exception);
        }
        return null;
    }

    private boolean validateJwt(Optional<String> value) {
        if (value.isPresent() == false) {
            return false;
        }
        try {
            Algorithm algorithm = Algorithm.HMAC256("secret");
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("masanori")
                    .build();
            DecodedJWT jwt = verifier.verify(value.get());
            Claim message = jwt.getClaim("message");
            if (message.isMissing() == false && message.isNull() == false) {
                logger.info("decoded message: " + message.asString());
            }
            return true;
        } catch (JWTVerificationException exception) {
            logger.error("JWT validate failed ", exception);
            return false;
        }
    }
...
```