Complete and refine OAuth + Traeffik Restriction

This commit is contained in:
mr
2026-02-20 10:30:34 +01:00
parent 078aae8172
commit 979747e288
10 changed files with 171 additions and 84 deletions

View File

@@ -27,8 +27,12 @@ type AuthConnector interface {
RevokeToken(token string, clientID string) error
RefreshToken(refreshToken string, clientID string) (*TokenResponse, error)
// Forward auth
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool
// CheckAuthForward validates the token and permissions for a forward auth request.
// Returns an HTTP status code:
// 200 — token active and permissions granted
// 401 — token missing, invalid, or inactive → caller should redirect to login
// 403 — token valid but permissions denied → caller should return forbidden
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) int
}
// Token is the unified token response returned to clients

View File

@@ -299,61 +299,61 @@ func (h *HydraConnector) RefreshToken(refreshToken string, clientID string) (*To
return &result, nil
}
// CheckAuthForward validates a JWT token for forward auth (Traefik integration)
// It introspects the token via Hydra and checks permissions from the token's extra claims
func (h *HydraConnector) CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool {
// CheckAuthForward validates a JWT token for forward auth (Traefik integration).
// It introspects the token via Hydra then checks permissions via Keto.
// Only requests from our own peer (external == false) are accepted.
// Returns 200 (OK), 401 (token inactive/invalid → redirect to login), or 403 (permission denied).
func (h *HydraConnector) CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) int {
if forward == "" || method == "" {
return false
return http.StatusUnauthorized
}
// Defense in depth: only SELF peer requests are allowed.
if external {
return http.StatusUnauthorized
}
logger := oclib.GetLogger()
// Introspect the token via Hydra to get claims
// Introspect the token via Hydra.
// An inactive or invalid token means the user must re-authenticate → 401.
result, err := h.Introspect(reqToken)
if err != nil || !result.Active {
if err != nil {
logger.Error().Msg("Forward auth introspect failed: " + err.Error())
}
return false
return http.StatusUnauthorized
}
// Extract claims from the introspection result's extra data
// Hydra puts consent session's access_token data in the "ext" field of introspection
// Build session claims from Hydra's introspection "ext" field.
// Hydra injects the consent session's access_token data there.
var sessionClaims claims.Claims
sessionClaims.Session.AccessToken = make(map[string]interface{})
sessionClaims.Session.IDToken = make(map[string]interface{})
if result.Extra != nil {
sessionClaims.Session.AccessToken = make(map[string]interface{})
sessionClaims.Session.IDToken = make(map[string]interface{})
for k, v := range result.Extra {
sessionClaims.Session.AccessToken[k] = v
}
}
// Also try to get id_token claims from the token if it's a JWT
// For now, use the introspected extra claims and the peer signature verification
if sessionClaims.Session.IDToken == nil {
sessionClaims.Session.IDToken = make(map[string]interface{})
}
// Get self peer for signature verification
// For SELF peer requests skip the signature check (internal traffic).
pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, fmt.Sprintf("%v", peer.SELF.EnumIndex()), false)
if len(pp.Data) > 0 {
p := pp.Data[0].(*peer.Peer)
// Re-sign for local verification if this is our own peer
if !external && p.PublicKey == publicKey {
if p.PublicKey == publicKey {
sessionClaims.Session.IDToken["signature"] = ""
// For internal requests, skip signature check by using the claims decoder directly
ok, err := claims.GetClaims().DecodeClaimsInToken(host, method, forward, sessionClaims, publicKey, external)
if err != nil {
logger.Error().Msg("Failed to decode claims: " + err.Error())
}
return ok
}
}
// Check permissions via Keto.
// A valid token with insufficient permissions → 403 (authenticated, not authorized).
ok, err := claims.GetClaims().DecodeClaimsInToken(host, method, forward, sessionClaims, publicKey, external)
if err != nil {
logger.Error().Msg("Failed to decode claims: " + err.Error())
logger.Error().Msg("Failed to decode claims in forward auth: " + err.Error())
return http.StatusForbidden
}
return ok
if !ok {
return http.StatusForbidden
}
return http.StatusOK
}
// extractBearerToken extracts the token from a "Bearer xxx" Authorization header value