Integration
Java
AgeOnce integration with Java (Spring Boot, Jakarta EE)
Java Integration
Complete AgeOnce integration example with Java.
Dependencies (Maven)
<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.22.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
</dependencies>Configuration
// AgeOnceConfig.java
@Configuration
public class AgeOnceConfig {
@Value("${ageonce.client-id}")
private String clientId;
@Value("${ageonce.client-secret}")
private String clientSecret;
@Value("${ageonce.redirect-uri}")
private String redirectUri;
@Value("${ageonce.app-url:https://app.ageonce.com}")
private String appUrl;
@Value("${ageonce.api-url:https://api.ageonce.com}")
private String apiUrl;
public String getClientId() { return clientId; }
public String getClientSecret() { return clientSecret; }
public String getRedirectUri() { return redirectUri; }
public String getAppUrl() { return appUrl; }
public String getApiUrl() { return apiUrl; }
}AgeOnce service
// AgeOnceService.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HexFormat;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class AgeOnceService {
private final AgeOnceConfig config;
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
public AgeOnceService(AgeOnceConfig config) {
this.config = config;
this.httpClient = HttpClient.newHttpClient();
this.objectMapper = new ObjectMapper();
}
public record VerifyUrlResult(String url, String state) {}
public VerifyUrlResult generateVerifyUrl() {
byte[] randomBytes = new byte[16];
new SecureRandom().nextBytes(randomBytes);
String state = HexFormat.of().formatHex(randomBytes);
String url = String.format(
"%s/?client_id=%s&redirect_uri=%s&state=%s",
config.getAppUrl(),
URLEncoder.encode(config.getClientId(), StandardCharsets.UTF_8),
URLEncoder.encode(config.getRedirectUri(), StandardCharsets.UTF_8),
state
);
return new VerifyUrlResult(url, state);
}
public Map<String, Object> exchangeCodeForToken(String code, String state) throws Exception {
Map<String, String> body = new java.util.HashMap<>();
body.put("grant_type", "authorization_code");
body.put("code", code);
body.put("redirect_uri", config.getRedirectUri());
if (state != null && !state.isEmpty()) {
body.put("state", state);
}
String credentials = Base64.getEncoder().encodeToString(
(config.getClientId() + ":" + config.getClientSecret()).getBytes(StandardCharsets.UTF_8)
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(config.getApiUrl() + "/api/oauth/token"))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + credentials)
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Token exchange failed");
}
return objectMapper.readValue(response.body(), Map.class);
}
public Map<String, Object> validateToken(String token) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(config.getApiUrl() + "/api/verify/token/" + URLEncoder.encode(token, StandardCharsets.UTF_8)))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
return Map.of("valid", false);
}
return objectMapper.readValue(response.body(), Map.class);
}
}Spring Boot Controller
// AgeOnceController.java
@Controller
public class AgeOnceController {
private final AgeOnceService ageOnceService;
public AgeOnceController(AgeOnceService ageOnceService) {
this.ageOnceService = ageOnceService;
}
@GetMapping("/verify")
public String verify(HttpSession session) {
var result = ageOnceService.generateVerifyUrl();
session.setAttribute("oauth_state", result.state());
return "redirect:" + result.url();
}
@GetMapping("/callback")
public String callback(
@RequestParam String code,
@RequestParam String state,
HttpSession session,
Model model) {
String expectedState = (String) session.getAttribute("oauth_state");
if (!state.equals(expectedState)) {
model.addAttribute("error", "Invalid state");
return "error";
}
try {
Map<String, Object> tokenData = ageOnceService.exchangeCodeForToken(code, state);
String accessToken = (String) tokenData.get("access_token");
Map<String, Object> result = ageOnceService.validateToken(accessToken);
if (Boolean.TRUE.equals(result.get("valid")) && Boolean.TRUE.equals(result.get("age_verified"))) {
session.setAttribute("age_verified", true);
session.setAttribute("age_over", result.get("age_over"));
return "redirect:/protected";
}
model.addAttribute("error", "Age verification failed");
return "error";
} catch (Exception e) {
model.addAttribute("error", e.getMessage());
return "error";
}
}
@GetMapping("/protected")
public String protectedPage(HttpSession session, Model model) {
if (!Boolean.TRUE.equals(session.getAttribute("age_verified"))) {
return "redirect:/verify";
}
return "protected";
}
}
// AgeVerificationInterceptor.java
@Component
public class AgeVerificationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession();
if (!Boolean.TRUE.equals(session.getAttribute("age_verified"))) {
response.sendRedirect("/verify");
return false;
}
return true;
}
}
// WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AgeVerificationInterceptor ageVerificationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ageVerificationInterceptor)
.addPathPatterns("/protected/**");
}
}// AgeOnceServlet.java
@WebServlet("/verify")
public class VerifyServlet extends HttpServlet {
@Inject
private AgeOnceService ageOnceService;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
var result = ageOnceService.generateVerifyUrl();
HttpSession session = request.getSession();
session.setAttribute("oauth_state", result.state());
response.sendRedirect(result.url());
}
}
@WebServlet("/callback")
public class CallbackServlet extends HttpServlet {
@Inject
private AgeOnceService ageOnceService;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String code = request.getParameter("code");
String state = request.getParameter("state");
HttpSession session = request.getSession();
String expectedState = (String) session.getAttribute("oauth_state");
if (!state.equals(expectedState)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid state");
return;
}
try {
Map<String, Object> tokenData = ageOnceService.exchangeCodeForToken(code, state);
String accessToken = (String) tokenData.get("access_token");
Map<String, Object> result = ageOnceService.validateToken(accessToken);
if (Boolean.TRUE.equals(result.get("valid")) && Boolean.TRUE.equals(result.get("age_verified"))) {
session.setAttribute("age_verified", true);
response.sendRedirect(request.getContextPath() + "/protected");
return;
}
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Age verification failed");
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}
// AgeVerificationFilter.java
@WebFilter("/protected/*")
public class AgeVerificationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
if (session == null || !Boolean.TRUE.equals(session.getAttribute("age_verified"))) {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/verify");
return;
}
chain.doFilter(request, response);
}
}Local JWT validation
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import java.net.URL;
@Service
public class AgeOnceJwtValidator {
private final JwkProvider jwkProvider;
public AgeOnceJwtValidator(AgeOnceConfig config) {
this.jwkProvider = new JwkProviderBuilder(new URL(config.getApiUrl() + "/api/.well-known/jwks.json"))
.cached(true)
.build();
}
public record ValidationResult(boolean valid, Map<String, Object> payload, String error) {}
public ValidationResult validate(String token) {
try {
// Issued tokens do not set kid. The published JWK id is key_2025_01.
var jwk = jwkProvider.get("key_2025_01");
var algorithm = Algorithm.RSA256((java.security.interfaces.RSAPublicKey) jwk.getPublicKey(), null);
var verifier = JWT.require(algorithm)
.withIssuer("https://ageonce.io")
.build();
DecodedJWT verified = verifier.verify(token);
Map<String, Object> payload = Map.of(
"age_verified", verified.getClaim("age_verified").asBoolean(),
"age_over", verified.getClaim("age_over").asInt()
);
return new ValidationResult(true, payload, null);
} catch (Exception e) {
return new ValidationResult(false, null, e.getMessage());
}
}
}Java provides strong typing and broad enterprise environment support.