Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1037,3 +1037,72 @@ Examples:
regex.extractAll('id:123, id:456', 'assa') == []

regex.extractAll('testuser@testdomain', '(.*)@([^.]*)') \\ Runtime Error multiple capture group

## JWT (JSON Web Token)

Returns a `cel.EnvOption` to configure support for JWT token verification, parsing, and claims inspection.

### Features & Capabilities

- **Automatic JWKS Key Retrieval**: Automatically retrieves public verification keys from standard OIDC discovery endpoints (`<issuer>/.well-known/jwks.json`) for major OAuth/OIDC identity providers (Auth0, Google, Apple, Okta, jwt.io).
- **Supported Signature Algorithms**:
- **HMAC**: `HS256`, `HS384`, `HS512`
- **RSA PKCS#1 v1.5**: `RS256`, `RS384`, `RS512`
- **RSA-PSS**: `PS256`, `PS384`, `PS512`
- **ECDSA**: `ES256` (P-256), `ES384` (P-384), `ES512` (P-521)
- **EdDSA**: `EdDSA` (Ed25519)
- **Key Formats Supported**: RSA public keys, EC public keys (P-256, P-384, P-521), OKP (Ed25519), PEM-encoded public keys/certificates, and `x5c` certificate chains.
- **Key Caching**: Thread-safe in-memory key caching (15-minute default TTL, configurable via `jwt.KeyCacheTTL`).
- **Key Filtering**: Automatically filters out non-signature keys (e.g. `use: "enc"`).
- **Bearer Prefix Handling**: Trims leading `Bearer ` prefixes automatically from token strings.

### Requirements & Limitations

- Tokens verified with `jwt.verify(<string>)` must contain an `iss` (issuer) claim to allow public key lookup.
- Cryptographic verification checks signature validity; audience and issuer validation should be performed via `jwt.Token.presentedBy(aud, iss)`.

### JWT Functions

#### jwt.verify

Verifies and parses a JWT token string using the configured key fetcher. Returns an `optional_type<jwt.Token>`.

jwt.verify(<string>) -> optional_type<jwt.Token>

Examples:

jwt.verify(tokenStr).hasValue()
jwt.verify(tokenStr).value().issuer == 'https://auth.example.com'

#### jwt.verifyWithKey

Verifies and parses a JWT token string using an explicit secret or PEM key string.

jwt.verifyWithKey(<string>, <string>) -> optional_type<jwt.Token>

Examples:

jwt.verifyWithKey(tokenStr, 'my-secret-key').hasValue()
jwt.verifyWithKey(tokenStr, pubKeyPem).value().subject == 'user_123'

#### jwt.Token.presentedBy

Determines whether the token was issued for the expected audience (`aud`) and issuer (`iss`). Works on both `jwt.Token` and `optional_type<jwt.Token>`.

<jwt.Token>.presentedBy(<string aud>, <string iss>) -> bool
<optional_type<jwt.Token>>.presentedBy(<string aud>, <string iss>) -> bool

Examples:

jwt.verify(tokenStr).presentedBy('my-api-audience', 'https://auth.example.com')

#### jwt.Token.claim

Queries a claim value by key name from the token payload, returning an `optional_type<string>`.

<jwt.Token>.claim(<string>) -> optional_type<string>

Examples:

jwt.verifyWithKey(tokenStr, key).value().claim('tenant_id').orValue('default')

35 changes: 35 additions & 0 deletions ext/jwt/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

package(
default_visibility = ["//visibility:public"],
licenses = ["notice"], # Apache 2.0
)

go_library(
name = "go_default_library",
srcs = [
"jwt.go",
],
importpath = "github.com/google/cel-go/ext/jwt",
deps = [
"//cel:go_default_library",
"//common/types:go_default_library",
"//common/types/ref:go_default_library",
"//common/types/traits:go_default_library",
],
)

go_test(
name = "go_default_test",
size = "small",
srcs = [
"export_test.go",
"jwt_test.go",
],
embed = [
":go_default_library",
],
deps = [
"//cel:go_default_library",
],
)
33 changes: 33 additions & 0 deletions ext/jwt/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jwt

import "time"

var (
DefaultJWKSKeyFetcher = defaultJWKSKeyFetcher
JwkToPEM = jwkToPEM
DecodeBase64Segment = decodeBase64Segment
ParseUnixTime = parseUnixTime
ParsePublicKey = parsePublicKey
VerifySignature = verifySignature
)

func NewJwtLib() *jwtLib {
return &jwtLib{
keyFetcher: defaultJWKSKeyFetcher,
cacheTTL: 15 * time.Minute,
}
}
17 changes: 17 additions & 0 deletions ext/jwt/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/google/cel-go/ext/jwt

go 1.23.0

require github.com/google/cel-go v0.30.0

require (
cel.dev/expr v0.25.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

replace github.com/google/cel-go => ../../
20 changes: 20 additions & 0 deletions ext/jwt/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Loading