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.api-url:https://app.ageonce.com}")
private String apiUrl;
// Getters...
}AgeOnce service
// AgeOnceService.java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.SecureRandom;
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/verify?client_id=%s&redirect_uri=%s&state=%s",
config.getApiUrl(),
config.getClientId(),
config.getRedirectUri(),
state
);
return new VerifyUrlResult(url, state);
}
public Map<String, Object> exchangeCodeForToken(String code) throws Exception {
Map<String, String> body = Map.of(
"client_id", config.getClientId(),
"client_secret", config.getClientSecret(),
"code", code,
"redirect_uri", config.getRedirectUri()
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(config.getApiUrl() + "/api/oauth/token"))
.header("Content-Type", "application/json")
.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 {
Map<String, String> body = Map.of("token", token);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(config.getApiUrl() + "/api/oauth/validate"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
.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);
String ageToken = (String) tokenData.get("age_token");
Map<String, Object> result = ageOnceService.validateToken(ageToken);
if (Boolean.TRUE.equals(result.get("valid"))) {
@SuppressWarnings("unchecked")
Map<String, Object> payload = (Map<String, Object>) result.get("payload");
if (Boolean.TRUE.equals(payload.get("age_verified"))) {
session.setAttribute("age_verified", true);
session.setAttribute("min_age", payload.get("min_age"));
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);
String ageToken = (String) tokenData.get("age_token");
Map<String, Object> result = ageOnceService.validateToken(ageToken);
if (Boolean.TRUE.equals(result.get("valid"))) {
@SuppressWarnings("unchecked")
Map<String, Object> payload = (Map<String, Object>) result.get("payload");
if (Boolean.TRUE.equals(payload.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;
@Service
public class AgeOnceJwtValidator {
private final JwkProvider jwkProvider;
public AgeOnceJwtValidator(AgeOnceConfig config) {
this.jwkProvider = new JwkProviderBuilder(config.getApiUrl() + "/api/oauth/jwks")
.cached(true)
.build();
}
public record ValidationResult(boolean valid, Map<String, Object> payload, String error) {}
public ValidationResult validate(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
var jwk = jwkProvider.get(jwt.getKeyId());
var algorithm = Algorithm.RSA256((java.security.interfaces.RSAPublicKey) jwk.getPublicKey(), null);
var verifier = JWT.require(algorithm)
.withIssuer("ageonce")
.build();
DecodedJWT verified = verifier.verify(token);
Map<String, Object> payload = Map.of(
"age_verified", verified.getClaim("age_verified").asBoolean(),
"min_age", verified.getClaim("min_age").asInt(),
"verified_at", verified.getClaim("verified_at").asString()
);
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.