Skip to content

Commit 5e9f62b

Browse files
committed
chore: more bug fixes for auth
1 parent c247d4b commit 5e9f62b

6 files changed

Lines changed: 113 additions & 8 deletions

File tree

sticker-award/infra/aws/lib/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class Api extends Construct {
8686
MESSAGING_PROVIDER: "aws",
8787
CATALOGUE_BASE_URL: `https://${props.serviceProps.cloudfrontDistribution.distributionDomainName}`,
8888
USER_REGISTERED_QUEUE_URL: userRegisteredQueue.queueUrl,
89-
OAUTH_ISSUER: `https://${props.serviceProps.cloudfrontDistribution.distributionDomainName}`,
89+
OAUTH_ISSUER: `https://${props.serviceProps.cloudfrontDistribution.distributionDomainName}/`,
9090
...props.serviceProps.messagingConfiguration.asEnvironmentVariables(),
9191
},
9292
secrets: secrets,

sticker-award/internal/api/middleware/jwt.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,22 @@ func NewJWTValidator(cfg *config.AuthConfig) (*JWTValidator, error) {
3939
// Create context with cancel for cleanup
4040
ctx, cancel := context.WithCancel(context.Background())
4141

42+
jwksUrl := cfg.JWKSUrl()
43+
log.WithFields(log.Fields{
44+
"issuer": cfg.Issuer,
45+
"jwksUrl": jwksUrl,
46+
}).Info("Initializing JWT validator")
47+
4248
// Create JWKS keyfunc with background refresh
43-
jwks, err := keyfunc.NewDefaultCtx(ctx, []string{cfg.JWKSUrl()})
49+
jwks, err := keyfunc.NewDefaultCtx(ctx, []string{jwksUrl})
4450
if err != nil {
4551
cancel()
52+
log.WithError(err).WithField("jwksUrl", jwksUrl).Error("Failed to fetch JWKS")
4653
return nil, err
4754
}
4855

56+
log.Info("JWT validator initialized successfully")
57+
4958
return &JWTValidator{
5059
jwks: jwks,
5160
issuer: cfg.Issuer,
@@ -85,7 +94,10 @@ func (v *JWTValidator) JWT() gin.HandlerFunc {
8594
)
8695

8796
if err != nil {
88-
log.WithContext(c.Request.Context()).WithError(err).Debug("Token validation failed")
97+
log.WithContext(c.Request.Context()).WithFields(log.Fields{
98+
"error": err.Error(),
99+
"expectedIssuer": v.issuer,
100+
}).Warn("Token validation failed")
89101
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
90102
"error": "invalid token",
91103
})

sticker-award/internal/config/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func (a *AuthConfig) JWKSUrl() string {
4747
if a.JwksUrl != "" {
4848
return a.JwksUrl
4949
}
50-
return a.Issuer + "/.well-known/jwks"
50+
// Normalize issuer (strip trailing slash) before appending path to avoid double slashes
51+
return strings.TrimSuffix(a.Issuer, "/") + "/.well-known/jwks"
5152
}
5253

5354
// ServerConfig holds HTTP server configuration
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2025-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadoghq.stickerlandia.stickercatalogue.security;
8+
9+
import jakarta.annotation.Priority;
10+
import jakarta.enterprise.context.ApplicationScoped;
11+
import jakarta.inject.Inject;
12+
import jakarta.ws.rs.Priorities;
13+
import jakarta.ws.rs.container.ContainerRequestContext;
14+
import jakarta.ws.rs.container.ContainerRequestFilter;
15+
import jakarta.ws.rs.core.Response;
16+
import jakarta.ws.rs.core.SecurityContext;
17+
import jakarta.ws.rs.ext.Provider;
18+
import org.eclipse.microprofile.config.inject.ConfigProperty;
19+
import org.eclipse.microprofile.jwt.JsonWebToken;
20+
import org.jboss.logging.Logger;
21+
22+
import java.io.IOException;
23+
import java.util.Optional;
24+
25+
/**
26+
* JAX-RS filter that validates JWT issuer with trailing slash normalization.
27+
* This is needed because OpenIddict adds a trailing slash to the issuer claim,
28+
* but we don't want double slashes in the JWKS URL derivation.
29+
*/
30+
@Provider
31+
@Priority(Priorities.AUTHENTICATION + 1) // Run after JWT authentication
32+
@ApplicationScoped
33+
public class IssuerValidationFilter implements ContainerRequestFilter {
34+
35+
private static final Logger LOG = Logger.getLogger(IssuerValidationFilter.class);
36+
37+
@Inject
38+
JsonWebToken jwt;
39+
40+
@ConfigProperty(name = "sticker.jwt.expected-issuer")
41+
Optional<String> expectedIssuer;
42+
43+
@Override
44+
public void filter(ContainerRequestContext requestContext) throws IOException {
45+
SecurityContext securityContext = requestContext.getSecurityContext();
46+
47+
// Skip if no authentication or no expected issuer configured
48+
if (securityContext == null || securityContext.getUserPrincipal() == null) {
49+
return;
50+
}
51+
52+
if (expectedIssuer.isEmpty()) {
53+
LOG.debug("No expected issuer configured, skipping issuer validation");
54+
return;
55+
}
56+
57+
// Get the issuer from the JWT
58+
String tokenIssuer = jwt.getIssuer();
59+
if (tokenIssuer == null) {
60+
LOG.warn("JWT has no issuer claim");
61+
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
62+
.entity("{\"error\": \"invalid token: missing issuer\"}")
63+
.build());
64+
return;
65+
}
66+
67+
// Normalize both issuers (strip trailing slash) for comparison
68+
String normalizedTokenIssuer = normalizeIssuer(tokenIssuer);
69+
String normalizedExpectedIssuer = normalizeIssuer(expectedIssuer.get());
70+
71+
if (!normalizedTokenIssuer.equals(normalizedExpectedIssuer)) {
72+
LOG.warnf("JWT issuer mismatch: token=%s, expected=%s", tokenIssuer, expectedIssuer.get());
73+
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
74+
.entity("{\"error\": \"invalid token: issuer mismatch\"}")
75+
.build());
76+
return;
77+
}
78+
79+
LOG.debugf("JWT issuer validated: %s", tokenIssuer);
80+
}
81+
82+
private String normalizeIssuer(String issuer) {
83+
if (issuer == null) {
84+
return "";
85+
}
86+
// Remove trailing slash for consistent comparison
87+
return issuer.endsWith("/") ? issuer.substring(0, issuer.length() - 1) : issuer;
88+
}
89+
}

sticker-catalogue/src/main/resources/application.properties

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ MESSAGING_PROVIDER=kafka
3232
quarkus.eventbridge.aws.region=${AWS_REGION:us-east-1}
3333
quarkus.eventbridge.aws.credentials.type=default
3434

35-
# JWT Configuration - JWKS URL derived from issuer
36-
mp.jwt.verify.issuer=${OAUTH_ISSUER:http://user-management:8080}
35+
# JWT Configuration
36+
# Note: Issuer validation is handled by IssuerValidationFilter to support trailing slash normalization
37+
# (OpenIddict adds trailing slash to issuer, but we don't want double slashes in JWKS URL)
3738
mp.jwt.verify.publickey.location=${OAUTH_ISSUER:http://user-management:8080}/.well-known/jwks
3839
smallrye.jwt.clock.skew=30
40+
# Expected issuer for custom validation (normalized to handle trailing slash)
41+
sticker.jwt.expected-issuer=${OAUTH_ISSUER:http://user-management:8080}
3942

4043
# Dev profile JWT overrides
41-
%dev.mp.jwt.verify.issuer=http://localhost:8080
4244
%dev.mp.jwt.verify.publickey.location=http://localhost:8080/.well-known/jwks
45+
%dev.sticker.jwt.expected-issuer=http://localhost:8080
4346

4447
# Kafka channel configuration - DISABLED by default
4548
# These settings are required because @Channel annotations cause build-time wiring.

web-frontend/src/components/StickerList.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const StickerList = () => {
1919
try {
2020
setLoading(true)
2121
const response = await authFetch(
22-
`${API_BASE_URL}/api/stickers/v1?page=${page}&size=${pageSize}`
22+
`${API_BASE_URL}/api/stickers/v1/?page=${page}&size=${pageSize}`
2323
)
2424
if (!response.ok) {
2525
throw new Error(`Failed to fetch stickers: ${response.status}`)

0 commit comments

Comments
 (0)