Repository: quarkusio/quarkus
Issue: io.quarkus.oidc.OidcSession uses Instant to present duration

### Describe the bug

In, io.quarkus.oidc.OidcSession (https://github.com/quarkusio/quarkus/blob/main/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java),
it uses Instant to present duration.

```
    /**
     * Return an {@linkplain:Instant} indicating how long will it take for the current session to expire.
     *
     * @return
     */
    Instant expiresIn();
```

Based on [Javadoc](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html), it is an instantaneous point on the time-line. But the comment says "how long", it should be Duration.

When using it in app, it seems that the value is filled with Duration too.
e.g. One value I got is `"expiresIn": "1970-01-01T00:23:05Z"`. I guess it means the session has 23 hours and 5 minutes to expire.

Personally, I prefer to use Instant but it should assigned the correct time value (a duration value is a little hard to predict the ending time without base time) and the comment should be updated.


### Expected behavior

Using Instant but it should assigned the correct time value (a duration value is a little hard to predict the ending time without base time) and the comment should be updated.

### Actual behavior

It uses Instant with the wrong value and comment is misleading.


### How to Reproduce?

Just any endpoint with injection of OidcSession.


### Output of `uname -a` or `ver`

Darwin Kernel Version 21.4.0

### Output of `java -version`

openjdk version "18" 2022-03-2

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.8.2.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Gradle 7.4.1

### Additional information

_No response_

Changed files:
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java
integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java
integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantHttps.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java

diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java
index 66fe190fa95..9f50af65aed 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcSession.java
@@ -1,5 +1,6 @@
 package io.quarkus.oidc;
 
+import java.time.Duration;
 import java.time.Instant;
 
 import org.eclipse.microprofile.jwt.JsonWebToken;
@@ -18,10 +19,31 @@ public interface OidcSession {
     /**
      * Return an {@linkplain:Instant} indicating how long will it take for the current session to expire.
      *
-     * @return
+     * @deprecated This method shouldn't be used as it provides an instant corresponding to 1970-01-01T0:0:0Z plus the duration
+     *             of the validity of the token, which is impractical. Please use either {@link #expiresAt()} or
+     *             {@link #validFor()} depending on your requirements. This method will be removed in a later version of
+     *             Quarkus.
+     *
+     * @return Instant
      */
+    @Deprecated(forRemoval = true, since = "2.12.0")
     Instant expiresIn();
 
+    /**
+     * Return an {@linkplain Instant} representing the current session's expiration time.
+     *
+     * @return Instant
+     */
+    Instant expiresAt();
+
+    /**
+     * Return a {@linkplain Duration} indicating how long the current session will remain valid for
+     * starting from this method's invocation time.
+     *
+     * @return Duration
+     */
+    Duration validFor();
+
     /**
      * Perform a local logout without a redirect to the OpenId Connect provider.
      *
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java
index 80f897682aa..af5e1e6b9a4 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java
@@ -1,5 +1,6 @@
 package io.quarkus.oidc.runtime;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Function;
 
@@ -56,9 +57,19 @@ public Instant expiresIn() {
         return Instant.ofEpochSecond(idToken.getExpirationTime() - nowSecs);
     }
 
+    @Override
+    public Instant expiresAt() {
+        return Instant.ofEpochSecond(idToken.getExpirationTime());
+    }
+
+    @Override
+    public Duration validFor() {
+        final long nowSecs = System.currentTimeMillis() / 1000;
+        return Duration.ofSeconds(idToken.getExpirationTime() - nowSecs);
+    }
+
     @Override
     public JsonWebToken getIdToken() {
         return idToken;
     }
-
 }
diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantHttps.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantHttps.java
index bca5b831aed..4f752143d3e 100644
--- a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantHttps.java
+++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantHttps.java
@@ -27,7 +27,8 @@ public String getTenant() {
     @Path("query")
     @Authenticated
     public String getTenantWithQuery(@QueryParam("code") String value) {
-        return getTenant() + "?code=" + value;
+        return getTenant() + "?code=" + value + "&expiresAt=" + session.expiresAt().getEpochSecond()
+                + "&expiresInDuration=" + session.validFor().getSeconds();
     }
 
     @GET
diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java
index 4c7b5516cd8..c1571712c69 100644
--- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java
+++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java
@@ -33,6 +33,7 @@
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
 import io.smallrye.jwt.util.KeyUtils;
+import io.vertx.core.json.JsonObject;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -256,10 +257,18 @@ public void testCodeFlowForceHttpsRedirectUriWithQueryAndPkce() throws Exception
             URI endpointLocationWithoutQueryUri = URI.create(endpointLocationWithoutQuery);
             assertEquals("code=b", endpointLocationWithoutQueryUri.getRawQuery());
 
-            page = webClient.getPage(endpointLocationWithoutQueryUri.toURL());
-            assertEquals("tenant-https:reauthenticated?code=b", page.getBody().asText());
             Cookie sessionCookie = getSessionCookie(webClient, "tenant-https_test");
             assertNotNull(sessionCookie);
+            JsonObject idToken = OidcUtils.decodeJwtContent(sessionCookie.getValue().split("\\\\|")[0]);
+            String expiresAt = idToken.getInteger("exp").toString();
+            page = webClient.getPage(endpointLocationWithoutQueryUri.toURL());
+            String response = page.getBody().asText();
+            assertTrue(
+                    response.startsWith("tenant-https:reauthenticated?code=b&expiresAt=" + expiresAt + "&expiresInDuration="));
+            Integer duration = Integer.valueOf(response.substring(response.length() - 1));
+            assertTrue(duration > 1 && duration < 5);
+            sessionCookie = getSessionCookie(webClient, "tenant-https_test");
+            assertNotNull(sessionCookie);
             webClient.getCookieManager().clearCookies();
         }
     }