diff --git a/apps/docs/content/guides/integrate/identity-providers/jwt_idp.mdx b/apps/docs/content/guides/integrate/identity-providers/jwt_idp.mdx
index 8b45b6340..ebf87cad3 100644
--- a/apps/docs/content/guides/integrate/identity-providers/jwt_idp.mdx
+++ b/apps/docs/content/guides/integrate/identity-providers/jwt_idp.mdx
@@ -61,6 +61,15 @@ the format specified in [RFC7517](https://datatracker.ietf.org/doc/html/rfc7517)
*Example: `https://issuer.test.internal/keys`*
+### Audience (optional)
+
+The expected audience (`aud` claim) in the JWT. If set, ZITADEL will validate that the JWT contains this audience.
+It's recommended to set this to ensure the JWT is intended to be used by ZITADEL.
+If the JWT contains multiple audiences, the configured audience must be one of them.
+If no audience is configured, ZITADEL will not validate the `aud` claim.
+
+*Example: `my-client`*
+
### JWT endpoint
Where ZITADEL redirects users to retrieve the JWT.
@@ -145,7 +154,7 @@ You have an existing application which exists in an internal network which handl
application is built using ZITADEL but the userbase of the existing application needs to be able to open the new
application from within the existing one without creating a new account or having to sign in.
-- Existing application: `apps.test.com/existing/`
+- Existing application: `apps.test.com/existing/` (also used as audience)
- Existing internal auth service: `issuer.test.internal`
- New application: `new.test.com`
- ZITADEL Login UI: `accounts.test.com`
@@ -163,6 +172,7 @@ application from within the existing one without creating a new account or havin
- Keys endpoint: `issuer.test.internal/keys`
- Header name: `x-custom-tkn`
- Issuer: `issuer.test.internal`
+ - Audience: `apps.test.com/existing/`
#### Result flow
diff --git a/backend/v3/domain/id_provider.go b/backend/v3/domain/id_provider.go
index 12a962bba..cae7b2c29 100644
--- a/backend/v3/domain/id_provider.go
+++ b/backend/v3/domain/id_provider.go
@@ -97,6 +97,7 @@ type JWT struct {
Issuer string `json:"issuer,omitempty"`
KeysEndpoint string `json:"keysEndpoint,omitempty"`
HeaderName string `json:"headerName,omitempty"`
+ Audience string `json:"audience,omitempty"`
}
type IDPJWT struct {
diff --git a/cmd/setup/71.go b/cmd/setup/71.go
new file mode 100644
index 000000000..f3a13477b
--- /dev/null
+++ b/cmd/setup/71.go
@@ -0,0 +1,27 @@
+package setup
+
+import (
+ "context"
+ _ "embed"
+
+ "github.com/zitadel/zitadel/internal/database"
+ "github.com/zitadel/zitadel/internal/eventstore"
+)
+
+var (
+ //go:embed 71.sql
+ addJWTAudience string
+)
+
+type JWTProvideAddAudienceColumn struct {
+ dbClient *database.DB
+}
+
+func (mig *JWTProvideAddAudienceColumn) Execute(ctx context.Context, _ eventstore.Event) error {
+ _, err := mig.dbClient.ExecContext(ctx, addJWTAudience)
+ return err
+}
+
+func (mig *JWTProvideAddAudienceColumn) String() string {
+ return "71_idp_templates6_jwt_add_audience"
+}
diff --git a/cmd/setup/71.sql b/cmd/setup/71.sql
new file mode 100644
index 000000000..65ed06b66
--- /dev/null
+++ b/cmd/setup/71.sql
@@ -0,0 +1,2 @@
+ALTER TABLE IF EXISTS projections.idp_templates6_jwt
+ADD COLUMN IF NOT EXISTS audience text;
diff --git a/cmd/setup/config.go b/cmd/setup/config.go
index bce60cfaa..e2ffe138f 100644
--- a/cmd/setup/config.go
+++ b/cmd/setup/config.go
@@ -186,6 +186,7 @@ type Steps struct {
s68TargetAddPayloadTypeColumn *TargetAddPayloadTypeColumn
s69CacheTablesLogged *CacheTablesLogged
s70AddEventStoreCommandEnforceOwner *AddEventStoreCommandEnforceOwnerColumn
+ s71JWTProvideAddAudienceColumn *JWTProvideAddAudienceColumn
RelationalTables *TransactionalTables
}
diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go
index faca769c0..78d1c2632 100644
--- a/cmd/setup/setup.go
+++ b/cmd/setup/setup.go
@@ -249,6 +249,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s68TargetAddPayloadTypeColumn = &TargetAddPayloadTypeColumn{dbClient: dbClient}
steps.s69CacheTablesLogged = &CacheTablesLogged{dbClient: dbClient}
steps.s70AddEventStoreCommandEnforceOwner = &AddEventStoreCommandEnforceOwnerColumn{dbClient: dbClient}
+ steps.s71JWTProvideAddAudienceColumn = &JWTProvideAddAudienceColumn{dbClient: dbClient}
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
if err != nil {
@@ -372,6 +373,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s59SetupWebkeys, // this step needs commands.
steps.s66SessionRecoveryCodeCheckedAt,
steps.s68TargetAddPayloadTypeColumn,
+ steps.s71JWTProvideAddAudienceColumn,
} {
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
if setupErr != nil {
diff --git a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.html b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.html
index 2ed95e033..36b1d7f88 100644
--- a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.html
+++ b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.html
@@ -56,6 +56,10 @@
{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}
+
+ {{ 'IDP.JWT.AUDIENCE' | translate }}
+
+
diff --git a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts
index 1964cc175..6bbf7089b 100644
--- a/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts
+++ b/console/src/app/modules/providers/provider-jwt/provider-jwt.component.ts
@@ -117,6 +117,7 @@ export class ProviderJWTComponent {
issuer: new UntypedFormControl('', [requiredValidator]),
jwtEndpoint: new UntypedFormControl('', [requiredValidator]),
keysEndpoint: new UntypedFormControl('', [requiredValidator]),
+ audience: new UntypedFormControl('' ),
});
this.authService
@@ -178,6 +179,7 @@ export class ProviderJWTComponent {
req.setIssuer(this.issuer?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.keysEndpoint?.value);
+ req.setAudience(this.audience?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service
@@ -204,6 +206,7 @@ export class ProviderJWTComponent {
req.setIssuer(this.issuer?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.keysEndpoint?.value);
+ req.setAudience(this.audience?.value);
req.setProviderOptions(this.options);
this.loading = true;
@@ -245,4 +248,8 @@ export class ProviderJWTComponent {
public get keysEndpoint(): AbstractControl | null {
return this.form.get('keysEndpoint');
}
+
+ public get audience(): AbstractControl | null {
+ return this.form.get('audience');
+ }
}
diff --git a/console/src/assets/i18n/ar.json b/console/src/assets/i18n/ar.json
index b853a3d17..9220bc703 100644
--- a/console/src/assets/i18n/ar.json
+++ b/console/src/assets/i18n/ar.json
@@ -2360,7 +2360,8 @@
"DESCRIPTION": "أدخل البيانات لمزود هوية JWT.",
"HEADERNAME": "اسم الرأس",
"JWTENDPOINT": "نقطة نهاية JWT",
- "JWTKEYSENDPOINT": "نقطة نهاية مفاتيح JWT"
+ "JWTKEYSENDPOINT": "نقطة نهاية مفاتيح JWT",
+ "AUDIENCE": "الجمهور"
},
"APPLE": {
"TEAMID": "معرف الفريق",
diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json
index d05da577e..c3450823e 100644
--- a/console/src/assets/i18n/bg.json
+++ b/console/src/assets/i18n/bg.json
@@ -2371,7 +2371,8 @@
"DESCRIPTION": "Въведете данните за JWT Identity Provider.",
"HEADERNAME": "Име на заглавката",
"JWTENDPOINT": "Крайна точка на JWT",
- "JWTKEYSENDPOINT": "Крайна точка на JWT ключове"
+ "JWTKEYSENDPOINT": "Крайна точка на JWT ключове",
+ "AUDIENCE": "Аудитория"
},
"APPLE": {
"TEAMID": "Team ID",
diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json
index cb9586746..7df3432d0 100644
--- a/console/src/assets/i18n/cs.json
+++ b/console/src/assets/i18n/cs.json
@@ -2363,7 +2363,8 @@
"DESCRIPTION": "Zadejte data pro poskytovatele identity JWT.",
"HEADERNAME": "Název hlavičky",
"JWTENDPOINT": "JWT koncový bod",
- "JWTKEYSENDPOINT": "Koncový bod klíčů JWT"
+ "JWTKEYSENDPOINT": "Koncový bod klíčů JWT",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "ID týmu",
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index c6ad5a3f5..5aa83ee40 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -2362,7 +2362,8 @@
"DESCRIPTION": "Geben Sie die Daten JWT Identitätsanbieter ein. ",
"HEADERNAME": "Header Name",
"JWTENDPOINT": "JWT Endpoint",
- "JWTKEYSENDPOINT": "JWT Keys Endpoint"
+ "JWTKEYSENDPOINT": "JWT Keys Endpoint",
+ "AUDIENCE": "Audience"
},
"TOAST": {
"SAVED": "Erfolgreich gespeichert.",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index 44e9210c6..c86694ae0 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -2371,7 +2371,8 @@
"DESCRIPTION": "Enter the data for JWT Identity Provider.",
"HEADERNAME": "Header Name",
"JWTENDPOINT": "JWT Endpoint",
- "JWTKEYSENDPOINT": "JWT Keys Endpoint"
+ "JWTKEYSENDPOINT": "JWT Keys Endpoint",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "Team ID",
diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json
index 65bd2a05e..ddaaf065d 100644
--- a/console/src/assets/i18n/es.json
+++ b/console/src/assets/i18n/es.json
@@ -2359,7 +2359,8 @@
"DESCRIPTION": "Introduce los datos para el proveedor de identidad JWT.",
"HEADERNAME": "Nombre de cabecera",
"JWTENDPOINT": "JWT Endpoint",
- "JWTKEYSENDPOINT": "JWT Keys Endpoint"
+ "JWTKEYSENDPOINT": "JWT Keys Endpoint",
+ "AUDIENCE": "Audience"
},
"TOAST": {
"SAVED": "Guardado con éxito.",
diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json
index 04f3b6ca0..4d7b2364c 100644
--- a/console/src/assets/i18n/fr.json
+++ b/console/src/assets/i18n/fr.json
@@ -2363,7 +2363,8 @@
"DESCRIPTION": "Entrez les données pour le fournisseur d'identité JWT.",
"HEADERNAME": "Nom de l'en-tête",
"JWTENDPOINT": "Point de terminaison JWT",
- "JWTKEYSENDPOINT": "Point de terminaison des clés JWT"
+ "JWTKEYSENDPOINT": "Point de terminaison des clés JWT",
+ "AUDIENCE": "Audience"
},
"TOAST": {
"SAVED": "Enregistré avec succès.",
diff --git a/console/src/assets/i18n/hu.json b/console/src/assets/i18n/hu.json
index 11e120920..91703e5d0 100644
--- a/console/src/assets/i18n/hu.json
+++ b/console/src/assets/i18n/hu.json
@@ -2364,7 +2364,8 @@
"DESCRIPTION": "Add meg az adatokat a JWT azonosítószolgáltatóhoz.",
"HEADERNAME": "Fejléc neve",
"JWTENDPOINT": "JWT végpont",
- "JWTKEYSENDPOINT": "JWT kulcsok végpont"
+ "JWTKEYSENDPOINT": "JWT kulcsok végpont",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "Csapatazonosító",
diff --git a/console/src/assets/i18n/id.json b/console/src/assets/i18n/id.json
index 536262f68..e8ce1915d 100644
--- a/console/src/assets/i18n/id.json
+++ b/console/src/assets/i18n/id.json
@@ -2364,7 +2364,8 @@
"DESCRIPTION": "Masukkan data Penyedia Identitas JWT.",
"HEADERNAME": "Nama Tajuk",
"JWTENDPOINT": "Titik Akhir JWT",
- "JWTKEYSENDPOINT": "Titik Akhir Kunci JWT"
+ "JWTKEYSENDPOINT": "Titik Akhir Kunci JWT",
+ "AUDIENCE": "Audiens"
},
"APPLE": {
"TEAMID": "ID tim",
diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json
index 109417d3c..1b95b8527 100644
--- a/console/src/assets/i18n/it.json
+++ b/console/src/assets/i18n/it.json
@@ -2363,7 +2363,8 @@
"DESCRIPTION": "Inserisci i dati per il JWT Identity Provider.",
"HEADERNAME": "Header Name",
"JWTENDPOINT": "JWT Endpoint",
- "JWTKEYSENDPOINT": "JWT Keys Endpoint"
+ "JWTKEYSENDPOINT": "JWT Keys Endpoint",
+ "AUDIENCE": "Audience"
},
"TOAST": {
"SAVED": "Salvato con successo.",
diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json
index b8ba3b374..c2e125c91 100644
--- a/console/src/assets/i18n/ja.json
+++ b/console/src/assets/i18n/ja.json
@@ -2366,7 +2366,8 @@
"DESCRIPTION": "JWT Identity Providerの情報を入力する。",
"HEADERNAME": "ヘッダー名",
"JWTENDPOINT": "JWTエンドポイント",
- "JWTKEYSENDPOINT": "JWTキーエンドポイント"
+ "JWTKEYSENDPOINT": "JWTキーエンドポイント",
+ "AUDIENCE": "オーディエンス"
},
"APPLE": {
"TEAMID": "チームID",
diff --git a/console/src/assets/i18n/ko.json b/console/src/assets/i18n/ko.json
index f20dc368a..d68cc73b8 100644
--- a/console/src/assets/i18n/ko.json
+++ b/console/src/assets/i18n/ko.json
@@ -2366,7 +2366,8 @@
"DESCRIPTION": "JWT ID 제공자에 필요한 데이터를 입력하세요.",
"HEADERNAME": "헤더 이름",
"JWTENDPOINT": "JWT 엔드포인트",
- "JWTKEYSENDPOINT": "JWT 키 엔드포인트"
+ "JWTKEYSENDPOINT": "JWT 키 엔드포인트",
+ "AUDIENCE": "청중"
},
"APPLE": {
"TEAMID": "팀 ID",
diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json
index 5fdedad94..0b9905255 100644
--- a/console/src/assets/i18n/mk.json
+++ b/console/src/assets/i18n/mk.json
@@ -2359,7 +2359,8 @@
"DESCRIPTION": "Внесете ги податоците за JWT IDP.",
"HEADERNAME": "Име на хедер",
"JWTENDPOINT": "JWT Крајна точка",
- "JWTKEYSENDPOINT": "JWT Крајна точка за клучеви"
+ "JWTKEYSENDPOINT": "JWT Крајна точка за клучеви",
+ "AUDIENCE": "Аудиториум"
},
"TOAST": {
"SAVED": "Успешно зачувано.",
diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json
index 237bff86e..b2d70ffaf 100644
--- a/console/src/assets/i18n/nl.json
+++ b/console/src/assets/i18n/nl.json
@@ -2366,7 +2366,8 @@
"DESCRIPTION": "Voer de gegevens in voor JWT Identity Provider.",
"HEADERNAME": "Header Naam",
"JWTENDPOINT": "JWT Eindpunt",
- "JWTKEYSENDPOINT": "JWT Sleutels Eindpunt"
+ "JWTKEYSENDPOINT": "JWT Sleutels Eindpunt",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "Team ID",
diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json
index f6f2df09a..888b48128 100644
--- a/console/src/assets/i18n/pl.json
+++ b/console/src/assets/i18n/pl.json
@@ -2362,7 +2362,8 @@
"DESCRIPTION": "Wprowadź dane dla dostawcy tożsamości JWT.",
"HEADERNAME": "Nazwa nagłówka",
"JWTENDPOINT": "Punkt końcowy JWT",
- "JWTKEYSENDPOINT": "Punkt końcowy kluczy JWT"
+ "JWTKEYSENDPOINT": "Punkt końcowy kluczy JWT",
+ "AUDIENCE": "Odbiorca"
},
"TOAST": {
"SAVED": "Zapisano pomyślnie.",
diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json
index cf4bbefb4..571d459e8 100644
--- a/console/src/assets/i18n/pt.json
+++ b/console/src/assets/i18n/pt.json
@@ -2358,7 +2358,8 @@
"DESCRIPTION": "Insira os dados para o Provedor de Identidade JWT.",
"HEADERNAME": "Nome do Cabeçalho",
"JWTENDPOINT": "Endpoint JWT",
- "JWTKEYSENDPOINT": "Endpoint de Chaves JWT"
+ "JWTKEYSENDPOINT": "Endpoint de Chaves JWT",
+ "AUDIENCE": "Audiência"
},
"TOAST": {
"SAVED": "Salvo com sucesso.",
diff --git a/console/src/assets/i18n/ro.json b/console/src/assets/i18n/ro.json
index ae88bf9a0..336709014 100644
--- a/console/src/assets/i18n/ro.json
+++ b/console/src/assets/i18n/ro.json
@@ -2364,7 +2364,8 @@
"DESCRIPTION": "Introduceți datele pentru furnizorul de identitate JWT.",
"HEADERNAME": "Nume antet",
"JWTENDPOINT": "Punct final JWT",
- "JWTKEYSENDPOINT": "Punct final chei JWT"
+ "JWTKEYSENDPOINT": "Punct final chei JWT",
+ "AUDIENCE": "Audiență"
},
"APPLE": {
"TEAMID": "ID echipă",
diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json
index dd11a3729..8cbbcc8cf 100644
--- a/console/src/assets/i18n/ru.json
+++ b/console/src/assets/i18n/ru.json
@@ -2469,7 +2469,8 @@
"DESCRIPTION": "Введите данные JWT для поставщика идентификационных данных .",
"HEADERNAME": "Название заголовка",
"JWTENDPOINT": "Конечная точка JWT",
- "JWTKEYSENDPOINT": "Конечная точка ключей JWT"
+ "JWTKEYSENDPOINT": "Конечная точка ключей JWT",
+ "AUDIENCE": "Аудитория"
},
"APPLE": {
"TEAMID": "Идентификатор команды",
diff --git a/console/src/assets/i18n/sv.json b/console/src/assets/i18n/sv.json
index 5022d7216..d4b41b439 100644
--- a/console/src/assets/i18n/sv.json
+++ b/console/src/assets/i18n/sv.json
@@ -2370,7 +2370,8 @@
"DESCRIPTION": "Ange data för JWT-identitetsleverantören.",
"HEADERNAME": "Header-namn",
"JWTENDPOINT": "JWT-slutpunkt",
- "JWTKEYSENDPOINT": "JWT-nycklar slutpunkt"
+ "JWTKEYSENDPOINT": "JWT-nycklar slutpunkt",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "Team-ID",
diff --git a/console/src/assets/i18n/tr.json b/console/src/assets/i18n/tr.json
index a931f4e72..446607971 100644
--- a/console/src/assets/i18n/tr.json
+++ b/console/src/assets/i18n/tr.json
@@ -2369,7 +2369,8 @@
"DESCRIPTION": "JWT Kimlik Sağlayıcısı için verileri girin.",
"HEADERNAME": "Başlık Adı",
"JWTENDPOINT": "JWT Uç Noktası",
- "JWTKEYSENDPOINT": "JWT Anahtarları Uç Noktası"
+ "JWTKEYSENDPOINT": "JWT Anahtarları Uç Noktası",
+ "AUDIENCE": "Audience"
},
"APPLE": {
"TEAMID": "Takım ID",
diff --git a/console/src/assets/i18n/uk.json b/console/src/assets/i18n/uk.json
index 8b3db0a64..139c03795 100644
--- a/console/src/assets/i18n/uk.json
+++ b/console/src/assets/i18n/uk.json
@@ -2370,7 +2370,8 @@
"DESCRIPTION": "Введіть дані для JWT постачальника ідентичності.",
"HEADERNAME": "Назва заголовка",
"JWTENDPOINT": "JWT кінцева точка",
- "JWTKEYSENDPOINT": "JWT кінцева точка ключів"
+ "JWTKEYSENDPOINT": "JWT кінцева точка ключів",
+ "AUDIENCE": "Аудиторія"
},
"APPLE": {
"TEAMID": "ID команди",
diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json
index 6ef1bf78a..7f82f148a 100644
--- a/console/src/assets/i18n/zh.json
+++ b/console/src/assets/i18n/zh.json
@@ -2374,7 +2374,8 @@
"DESCRIPTION": "输入 JWT 身份提供者的数据。",
"HEADERNAME": "请求头名称",
"JWTENDPOINT": "JWT 端点",
- "JWTKEYSENDPOINT": "JWT Keys 端点"
+ "JWTKEYSENDPOINT": "JWT Keys 端点",
+ "AUDIENCE": "受众"
},
"APPLE": {
"TEAMID": "Team ID",
diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go
index 6841cadb5..1f1a104a6 100644
--- a/internal/api/grpc/admin/idp_converter.go
+++ b/internal/api/grpc/admin/idp_converter.go
@@ -265,22 +265,24 @@ func updateGenericOIDCProviderToCommand(req *admin_pb.UpdateGenericOIDCProviderR
func addJWTProviderToCommand(req *admin_pb.AddJWTProviderRequest) command.JWTProvider {
return command.JWTProvider{
- Name: req.Name,
- Issuer: req.Issuer,
- JWTEndpoint: req.JwtEndpoint,
- KeyEndpoint: req.KeysEndpoint,
- HeaderName: req.HeaderName,
+ Name: req.GetName(),
+ Issuer: req.GetIssuer(),
+ JWTEndpoint: req.GetJwtEndpoint(),
+ KeyEndpoint: req.GetKeysEndpoint(),
+ HeaderName: req.GetHeaderName(),
+ Audience: req.GetAudience(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateJWTProviderToCommand(req *admin_pb.UpdateJWTProviderRequest) command.JWTProvider {
return command.JWTProvider{
- Name: req.Name,
- Issuer: req.Issuer,
- JWTEndpoint: req.JwtEndpoint,
- KeyEndpoint: req.KeysEndpoint,
- HeaderName: req.HeaderName,
+ Name: req.GetName(),
+ Issuer: req.GetIssuer(),
+ JWTEndpoint: req.GetJwtEndpoint(),
+ KeyEndpoint: req.GetKeysEndpoint(),
+ HeaderName: req.GetHeaderName(),
+ Audience: req.GetAudience(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go
index 6e8f14359..b88400922 100644
--- a/internal/api/grpc/idp/converter.go
+++ b/internal/api/grpc/idp/converter.go
@@ -529,6 +529,7 @@ func jwtConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.JWTIDP
Issuer: template.Issuer,
KeysEndpoint: template.KeysEndpoint,
HeaderName: template.HeaderName,
+ Audience: template.Audience,
},
}
}
diff --git a/internal/api/grpc/idp/v2/query.go b/internal/api/grpc/idp/v2/query.go
index c9c763cb9..8c9a892a7 100644
--- a/internal/api/grpc/idp/v2/query.go
+++ b/internal/api/grpc/idp/v2/query.go
@@ -197,6 +197,7 @@ func jwtConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.JWTIDPTemplate)
Issuer: template.Issuer,
KeysEndpoint: template.KeysEndpoint,
HeaderName: template.HeaderName,
+ Audience: template.Audience,
},
}
}
diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go
index a2033f5af..2ad46df01 100644
--- a/internal/api/grpc/management/idp_converter.go
+++ b/internal/api/grpc/management/idp_converter.go
@@ -258,22 +258,24 @@ func updateGenericOIDCProviderToCommand(req *mgmt_pb.UpdateGenericOIDCProviderRe
func addJWTProviderToCommand(req *mgmt_pb.AddJWTProviderRequest) command.JWTProvider {
return command.JWTProvider{
- Name: req.Name,
- Issuer: req.Issuer,
- JWTEndpoint: req.JwtEndpoint,
- KeyEndpoint: req.KeysEndpoint,
- HeaderName: req.HeaderName,
+ Name: req.GetName(),
+ Issuer: req.GetIssuer(),
+ JWTEndpoint: req.GetJwtEndpoint(),
+ KeyEndpoint: req.GetKeysEndpoint(),
+ HeaderName: req.GetHeaderName(),
+ Audience: req.GetAudience(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateJWTProviderToCommand(req *mgmt_pb.UpdateJWTProviderRequest) command.JWTProvider {
return command.JWTProvider{
- Name: req.Name,
- Issuer: req.Issuer,
- JWTEndpoint: req.JwtEndpoint,
- KeyEndpoint: req.KeysEndpoint,
- HeaderName: req.HeaderName,
+ Name: req.GetName(),
+ Issuer: req.GetIssuer(),
+ JWTEndpoint: req.GetJwtEndpoint(),
+ KeyEndpoint: req.GetKeysEndpoint(),
+ HeaderName: req.GetHeaderName(),
+ Audience: req.GetAudience(),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go
index 611d795a1..00a02b5d2 100644
--- a/internal/api/ui/login/external_provider_handler.go
+++ b/internal/api/ui/login/external_provider_handler.go
@@ -1090,6 +1090,7 @@ func (l *Login) jwtProvider(identityProvider *query.IDPTemplate) (*jwt.Provider,
identityProvider.JWTIDPTemplate.Endpoint,
identityProvider.JWTIDPTemplate.KeysEndpoint,
identityProvider.JWTIDPTemplate.HeaderName,
+ identityProvider.JWTIDPTemplate.Audience,
l.idpConfigAlg,
)
}
diff --git a/internal/command/idp.go b/internal/command/idp.go
index 1585528a8..b7bc219a2 100644
--- a/internal/command/idp.go
+++ b/internal/command/idp.go
@@ -41,6 +41,7 @@ type JWTProvider struct {
JWTEndpoint string
KeyEndpoint string
HeaderName string
+ Audience string
IDPOptions idp.Options
}
diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go
index c8f73defe..1dd389fe6 100644
--- a/internal/command/idp_model.go
+++ b/internal/command/idp_model.go
@@ -430,6 +430,7 @@ type JWTIDPWriteModel struct {
JWTEndpoint string
KeysEndpoint string
HeaderName string
+ Audience string
idp.Options
State domain.IDPState
@@ -465,6 +466,7 @@ func (wm *JWTIDPWriteModel) reduceAddedEvent(e *idp.JWTIDPAddedEvent) {
wm.JWTEndpoint = e.JWTEndpoint
wm.KeysEndpoint = e.KeysEndpoint
wm.HeaderName = e.HeaderName
+ wm.Audience = e.Audience
wm.Options = e.Options
wm.State = domain.IDPStateActive
}
@@ -485,6 +487,9 @@ func (wm *JWTIDPWriteModel) reduceChangedEvent(e *idp.JWTIDPChangedEvent) {
if e.HeaderName != nil {
wm.HeaderName = *e.HeaderName
}
+ if e.Audience != nil {
+ wm.Audience = *e.Audience
+ }
wm.Options.ReduceChanges(e.OptionChanges)
}
@@ -493,7 +498,8 @@ func (wm *JWTIDPWriteModel) NewChanges(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options idp.Options,
) ([]idp.JWTIDPChanges, error) {
changes := make([]idp.JWTIDPChanges, 0)
@@ -512,6 +518,9 @@ func (wm *JWTIDPWriteModel) NewChanges(
if wm.HeaderName != headerName {
changes = append(changes, idp.ChangeJWTHeaderName(headerName))
}
+ if wm.Audience != audience {
+ changes = append(changes, idp.ChangeJWTAudience(audience))
+ }
opts := wm.Options.Changes(options)
if !opts.IsZero() {
changes = append(changes, idp.ChangeJWTOptions(opts))
@@ -583,6 +592,7 @@ func (wm *JWTIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encrypt
wm.JWTEndpoint,
wm.KeysEndpoint,
wm.HeaderName,
+ wm.Audience,
idpAlg,
opts...,
)
diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go
index eb1c05621..2e8c37856 100644
--- a/internal/command/instance_idp.go
+++ b/internal/command/instance_idp.go
@@ -940,6 +940,7 @@ func (c *Commands) prepareAddInstanceJWTProvider(a *instance.Aggregate, writeMod
provider.JWTEndpoint,
provider.KeyEndpoint,
provider.HeaderName,
+ provider.Audience,
provider.IDPOptions,
),
}, nil
@@ -988,6 +989,7 @@ func (c *Commands) prepareUpdateInstanceJWTProvider(a *instance.Aggregate, write
provider.JWTEndpoint,
provider.KeyEndpoint,
provider.HeaderName,
+ provider.Audience,
provider.IDPOptions,
)
if err != nil || event == nil {
diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go
index 00d98d5ef..5857ae46f 100644
--- a/internal/command/instance_idp_model.go
+++ b/internal/command/instance_idp_model.go
@@ -274,7 +274,8 @@ func (wm *InstanceJWTIDPWriteModel) NewChangedEvent(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options idp.Options,
) (*instance.JWTIDPChangedEvent, error) {
@@ -284,6 +285,7 @@ func (wm *InstanceJWTIDPWriteModel) NewChangedEvent(
jwtEndpoint,
keysEndpoint,
headerName,
+ audience,
options,
)
if err != nil || len(changes) == 0 {
diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go
index 912f321f8..735d40e32 100644
--- a/internal/command/org_idp.go
+++ b/internal/command/org_idp.go
@@ -911,6 +911,7 @@ func (c *Commands) prepareAddOrgJWTProvider(a *org.Aggregate, writeModel *OrgJWT
provider.JWTEndpoint,
provider.KeyEndpoint,
provider.HeaderName,
+ provider.Audience,
provider.IDPOptions,
),
}, nil
@@ -959,6 +960,7 @@ func (c *Commands) prepareUpdateOrgJWTProvider(a *org.Aggregate, writeModel *Org
provider.JWTEndpoint,
provider.KeyEndpoint,
provider.HeaderName,
+ provider.Audience,
provider.IDPOptions,
)
if err != nil || event == nil {
diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go
index c0adb63bf..f8940f159 100644
--- a/internal/command/org_idp_model.go
+++ b/internal/command/org_idp_model.go
@@ -276,7 +276,8 @@ func (wm *OrgJWTIDPWriteModel) NewChangedEvent(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options idp.Options,
) (*org.JWTIDPChangedEvent, error) {
@@ -286,6 +287,7 @@ func (wm *OrgJWTIDPWriteModel) NewChangedEvent(
jwtEndpoint,
keysEndpoint,
headerName,
+ audience,
options,
)
if err != nil || len(changes) == 0 {
diff --git a/internal/idp/providers/jwt/jwt.go b/internal/idp/providers/jwt/jwt.go
index d972102b0..a58be0aa3 100644
--- a/internal/idp/providers/jwt/jwt.go
+++ b/internal/idp/providers/jwt/jwt.go
@@ -28,6 +28,7 @@ type Provider struct {
issuer string
jwtEndpoint string
keysEndpoint string
+ audience string
isLinkingAllowed bool
isCreationAllowed bool
isAutoCreation bool
@@ -67,13 +68,14 @@ func WithAutoUpdate() ProviderOpts {
}
// New creates a JWT provider
-func New(name, issuer, jwtEndpoint, keysEndpoint, headerName string, encryptionAlg crypto.EncryptionAlgorithm, options ...ProviderOpts) (*Provider, error) {
+func New(name, issuer, jwtEndpoint, keysEndpoint, headerName, audience string, encryptionAlg crypto.EncryptionAlgorithm, options ...ProviderOpts) (*Provider, error) {
provider := &Provider{
name: name,
issuer: issuer,
jwtEndpoint: jwtEndpoint,
keysEndpoint: keysEndpoint,
headerName: headerName,
+ audience: audience,
encryptionAlg: encryptionAlg,
}
for _, option := range options {
diff --git a/internal/idp/providers/jwt/jwt_test.go b/internal/idp/providers/jwt/jwt_test.go
index aba337d2e..ccbfdaa3f 100644
--- a/internal/idp/providers/jwt/jwt_test.go
+++ b/internal/idp/providers/jwt/jwt_test.go
@@ -20,6 +20,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
+ audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
}
type args struct {
@@ -43,6 +44,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -64,6 +66,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -83,6 +86,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -108,6 +112,7 @@ func TestProvider_BeginAuth(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
+ tt.fields.audience,
tt.fields.encryptionAlg(t),
)
require.NoError(t, err)
@@ -135,6 +140,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
+ audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
opts []ProviderOpts
}
@@ -159,6 +165,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -181,6 +188,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -211,6 +219,7 @@ func TestProvider_Options(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
+ tt.fields.audience,
tt.fields.encryptionAlg(t),
tt.fields.opts...,
)
diff --git a/internal/idp/providers/jwt/session.go b/internal/idp/providers/jwt/session.go
index 09cea2b5a..820cf0645 100644
--- a/internal/idp/providers/jwt/session.go
+++ b/internal/idp/providers/jwt/session.go
@@ -100,6 +100,11 @@ func (s *Session) validateToken(ctx context.Context, token string) (*oidc.IDToke
return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err)
}
+ if s.Provider.audience != "" {
+ if err = oidc.CheckAudience(claims, s.Provider.audience); err != nil {
+ return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err)
+ }
+ }
return claims, nil
}
diff --git a/internal/idp/providers/jwt/session_test.go b/internal/idp/providers/jwt/session_test.go
index a6bcee0fa..daf82befe 100644
--- a/internal/idp/providers/jwt/session_test.go
+++ b/internal/idp/providers/jwt/session_test.go
@@ -27,6 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
+ audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
httpMock func(issuer string)
authURL string
@@ -60,6 +61,7 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -83,6 +85,36 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "",
+ encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
+ return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
+ },
+ httpMock: func(issuer string) {
+ gock.New(issuer).
+ Get("/keys").
+ Reply(200).
+ JSON(keys(t))
+ },
+ authURL: "https://auth.com/jwt?authRequestID=testState",
+ tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
+ Token: &oauth2.Token{},
+ IDToken: "invalidToken",
+ },
+ },
+ want: want{
+ err: func(err error) bool {
+ return errors.Is(err, ErrInvalidToken)
+ },
+ },
+ },
+ {
+ name: "invalid token (audience)",
+ fields: fields{
+ issuer: "https://jwt.com",
+ jwtEndpoint: "https://auth.com/jwt",
+ keysEndpoint: "https://jwt.com/keys",
+ headerName: "jwt-header",
+ audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -214,6 +246,69 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
+ audience: "clientID",
+ encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
+ return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
+ },
+ httpMock: func(issuer string) {
+ gock.New(issuer).
+ Get("/keys").
+ Reply(200).
+ JSON(keys(t))
+ },
+ authURL: "https://auth.com/jwt?authRequestID=testState",
+ tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
+ Token: &oauth2.Token{},
+ IDToken: idToken(t, "https://jwt.com", time.Now().Add(time.Hour), 0),
+ IDTokenClaims: &oidc.IDTokenClaims{
+ TokenClaims: oidc.TokenClaims{
+ Subject: "sub",
+ },
+ UserInfoProfile: oidc.UserInfoProfile{
+ Picture: "picture",
+ Name: "firstname lastname",
+ GivenName: "firstname",
+ FamilyName: "lastname",
+ Nickname: "nickname",
+ PreferredUsername: "username",
+ Profile: "profile",
+ Locale: oidc.NewLocale(language.English),
+ },
+ UserInfoEmail: oidc.UserInfoEmail{
+ Email: "email",
+ EmailVerified: oidc.Bool(true),
+ },
+ UserInfoPhone: oidc.UserInfoPhone{
+ PhoneNumber: "phone",
+ PhoneNumberVerified: true,
+ },
+ },
+ },
+ },
+ want: want{
+ id: "sub",
+ firstName: "firstname",
+ lastName: "lastname",
+ displayName: "firstname lastname",
+ nickName: "nickname",
+ preferredUsername: "username",
+ email: "email",
+ isEmailVerified: true,
+ phone: "phone",
+ isPhoneVerified: true,
+ preferredLanguage: language.English,
+ avatarURL: "picture",
+ profile: "profile",
+ },
+ },
+ {
+ name: "successful fetch (no audience check)",
+ fields: fields{
+ issuer: "https://jwt.com",
+ jwtEndpoint: "https://auth.com/jwt",
+ keysEndpoint: "https://jwt.com/keys",
+ headerName: "jwt-header",
+ audience: "",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -281,6 +376,7 @@ func TestSession_FetchUser(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
+ tt.fields.audience,
tt.fields.encryptionAlg(t),
)
require.NoError(t, err)
diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go
index 25c32a140..35b8c18c7 100644
--- a/internal/query/idp_template.go
+++ b/internal/query/idp_template.go
@@ -82,6 +82,7 @@ type JWTIDPTemplate struct {
KeysEndpoint string
HeaderName string
Endpoint string
+ Audience string
}
type AzureADIDPTemplate struct {
@@ -355,6 +356,10 @@ var (
name: projection.JWTHeaderNameCol,
table: jwtIdpTemplateTable,
}
+ JWTAudienceCol = Column{
+ name: projection.JWTAudienceCol,
+ table: jwtIdpTemplateTable,
+ }
)
var (
@@ -913,6 +918,7 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
JWTEndpointCol.identifier(),
JWTKeysEndpointCol.identifier(),
JWTHeaderNameCol.identifier(),
+ JWTAudienceCol.identifier(),
// azure
AzureADIDCol.identifier(),
AzureADClientIDCol.identifier(),
@@ -1034,6 +1040,7 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
jwtEndpoint := sql.NullString{}
jwtKeysEndpoint := sql.NullString{}
jwtHeaderName := sql.NullString{}
+ jwtAudience := sql.NullString{}
azureadID := sql.NullString{}
azureadClientID := sql.NullString{}
@@ -1153,6 +1160,7 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
&jwtEndpoint,
&jwtKeysEndpoint,
&jwtHeaderName,
+ &jwtAudience,
// azure
&azureadID,
&azureadClientID,
@@ -1273,6 +1281,7 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
KeysEndpoint: jwtKeysEndpoint.String,
HeaderName: jwtHeaderName.String,
Endpoint: jwtEndpoint.String,
+ Audience: jwtAudience.String,
}
}
if azureadID.Valid {
@@ -1429,6 +1438,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
JWTEndpointCol.identifier(),
JWTKeysEndpointCol.identifier(),
JWTHeaderNameCol.identifier(),
+ JWTAudienceCol.identifier(),
// azure
AzureADIDCol.identifier(),
AzureADClientIDCol.identifier(),
@@ -1555,6 +1565,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
jwtEndpoint := sql.NullString{}
jwtKeysEndpoint := sql.NullString{}
jwtHeaderName := sql.NullString{}
+ jwtAudience := sql.NullString{}
azureadID := sql.NullString{}
azureadClientID := sql.NullString{}
@@ -1674,6 +1685,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
&jwtEndpoint,
&jwtKeysEndpoint,
&jwtHeaderName,
+ &jwtAudience,
// azure
&azureadID,
&azureadClientID,
@@ -1793,6 +1805,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
KeysEndpoint: jwtKeysEndpoint.String,
HeaderName: jwtHeaderName.String,
Endpoint: jwtEndpoint.String,
+ Audience: jwtAudience.String,
}
}
if azureadID.Valid {
diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go
index e730a765b..cc16ab8f5 100644
--- a/internal/query/idp_template_test.go
+++ b/internal/query/idp_template_test.go
@@ -54,6 +54,7 @@ var (
` projections.idp_templates6_jwt.jwt_endpoint,` +
` projections.idp_templates6_jwt.keys_endpoint,` +
` projections.idp_templates6_jwt.header_name,` +
+ ` projections.idp_templates6_jwt.audience,` +
// azure
` projections.idp_templates6_azure.idp_id,` +
` projections.idp_templates6_azure.client_id,` +
@@ -185,6 +186,7 @@ var (
"jwt_endpoint",
"keys_endpoint",
"header_name",
+ "audience",
// azure
"idp_id",
"client_id",
@@ -303,6 +305,7 @@ var (
` projections.idp_templates6_jwt.jwt_endpoint,` +
` projections.idp_templates6_jwt.keys_endpoint,` +
` projections.idp_templates6_jwt.header_name,` +
+ ` projections.idp_templates6_jwt.audience,` +
// azure
` projections.idp_templates6_azure.idp_id,` +
` projections.idp_templates6_azure.client_id,` +
@@ -435,6 +438,7 @@ var (
"jwt_endpoint",
"keys_endpoint",
"header_name",
+ "audience",
// azure
"idp_id",
"client_id",
@@ -593,6 +597,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -749,6 +754,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -903,6 +909,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt",
"keys",
"header",
+ "audience",
// azure
nil,
nil,
@@ -1006,6 +1013,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
Endpoint: "jwt",
KeysEndpoint: "keys",
HeaderName: "header",
+ Audience: "audience",
},
},
},
@@ -1055,6 +1063,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1206,6 +1215,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1357,6 +1367,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1509,6 +1520,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1660,6 +1672,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1817,6 +1830,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -1989,6 +2003,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2142,6 +2157,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2323,6 +2339,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2504,6 +2521,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2658,6 +2676,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2777,6 +2796,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -2896,6 +2916,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -3015,6 +3036,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -3134,6 +3156,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
+ nil,
// azure
nil,
nil,
@@ -3253,6 +3276,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt",
"keys",
"header",
+ "audience",
// azure
nil,
nil,
@@ -3508,6 +3532,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
Endpoint: "jwt",
KeysEndpoint: "keys",
HeaderName: "header",
+ Audience: "audience",
},
},
},
diff --git a/internal/query/projection/idp_jwt_relational.go b/internal/query/projection/idp_jwt_relational.go
index 367585921..3bf4782ba 100644
--- a/internal/query/projection/idp_jwt_relational.go
+++ b/internal/query/projection/idp_jwt_relational.go
@@ -221,5 +221,9 @@ func (p *relationalTablesProjection) reduceJWTIDPChangedColumns(payload *domain.
payloadChanged = true
payload.Issuer = *idpEvent.Issuer
}
+ if idpEvent.Audience != nil && *idpEvent.Audience != payload.Audience {
+ payloadChanged = true
+ payload.Audience = *idpEvent.Audience
+ }
return payloadChanged
}
diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go
index b14fc5ef3..b564ee19f 100644
--- a/internal/query/projection/idp_template.go
+++ b/internal/query/projection/idp_template.go
@@ -89,6 +89,7 @@ const (
JWTEndpointCol = "jwt_endpoint"
JWTKeysEndpointCol = "keys_endpoint"
JWTHeaderNameCol = "header_name"
+ JWTAudienceCol = "audience"
AzureADIDCol = "idp_id"
AzureADInstanceIDCol = "instance_id"
@@ -258,6 +259,7 @@ func (*idpTemplateProjection) Init() *old_handler.Check {
handler.NewColumn(JWTEndpointCol, handler.ColumnTypeText),
handler.NewColumn(JWTKeysEndpointCol, handler.ColumnTypeText),
handler.NewColumn(JWTHeaderNameCol, handler.ColumnTypeText, handler.Nullable()),
+ handler.NewColumn(JWTAudienceCol, handler.ColumnTypeText, handler.Nullable()),
},
handler.NewPrimaryKey(JWTInstanceIDCol, JWTIDCol),
IDPTemplateJWTSuffix,
@@ -1046,6 +1048,7 @@ func (p *idpTemplateProjection) reduceJWTIDPAdded(event eventstore.Event) (*hand
handler.NewCol(JWTEndpointCol, idpEvent.JWTEndpoint),
handler.NewCol(JWTKeysEndpointCol, idpEvent.KeysEndpoint),
handler.NewCol(JWTHeaderNameCol, idpEvent.HeaderName),
+ handler.NewCol(JWTAudienceCol, idpEvent.Audience),
},
handler.WithTableSuffix(IDPTemplateJWTSuffix),
),
@@ -2340,6 +2343,9 @@ func reduceJWTIDPChangedColumns(idpEvent idp.JWTIDPChangedEvent) []handler.Colum
if idpEvent.Issuer != nil {
jwtCols = append(jwtCols, handler.NewCol(JWTIssuerCol, *idpEvent.Issuer))
}
+ if idpEvent.Audience != nil {
+ jwtCols = append(jwtCols, handler.NewCol(JWTAudienceCol, *idpEvent.Audience))
+ }
return jwtCols
}
diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go
index 03a634ff4..b7a4d0233 100644
--- a/internal/query/projection/idp_template_test.go
+++ b/internal/query/projection/idp_template_test.go
@@ -4280,7 +4280,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true,
- "autoLinkingOption": 1
+ "autoLinkingOption": 1,
+ "audience": "audience"
}`),
), instance.JWTIDPAddedEventMapper),
},
@@ -4311,7 +4312,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
},
},
{
- expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)",
+ expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name, audience) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
@@ -4319,6 +4320,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
"jwt",
"keys",
"header",
+ "audience",
},
},
},
@@ -4342,7 +4344,8 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true,
- "autoLinkingOption": 1
+ "autoLinkingOption": 1,
+ "audience": "audience"
}`),
), org.JWTIDPAddedEventMapper),
},
@@ -4373,7 +4376,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
},
},
{
- expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name) VALUES ($1, $2, $3, $4, $5, $6)",
+ expectedStmt: "INSERT INTO projections.idp_templates6_jwt (idp_id, instance_id, issuer, jwt_endpoint, keys_endpoint, header_name, audience) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
@@ -4381,6 +4384,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
"jwt",
"keys",
"header",
+ "audience",
},
},
},
@@ -4442,6 +4446,7 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
"jwtEndpoint": "jwt",
"keysEndpoint": "keys",
"headerName": "header",
+ "audience": "audience",
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
@@ -4471,12 +4476,13 @@ func TestIDPTemplateProjection_reducesJWT(t *testing.T) {
},
},
{
- expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer) = ($1, $2, $3, $4) WHERE (idp_id = $5) AND (instance_id = $6)",
+ expectedStmt: "UPDATE projections.idp_templates6_jwt SET (jwt_endpoint, keys_endpoint, header_name, issuer, audience) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
expectedArgs: []interface{}{
"jwt",
"keys",
"header",
"issuer",
+ "audience",
"idp-id",
"instance-id",
},
diff --git a/internal/repository/idp/jwt.go b/internal/repository/idp/jwt.go
index 7fe82e7c0..a80e54ace 100644
--- a/internal/repository/idp/jwt.go
+++ b/internal/repository/idp/jwt.go
@@ -14,6 +14,7 @@ type JWTIDPAddedEvent struct {
JWTEndpoint string `json:"jwtEndpoint,omitempty"`
KeysEndpoint string `json:"keysEndpoint,omitempty"`
HeaderName string `json:"headerName,omitempty"`
+ Audience string `json:"audience,omitempty"`
Options
}
@@ -24,7 +25,8 @@ func NewJWTIDPAddedEvent(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options Options,
) *JWTIDPAddedEvent {
return &JWTIDPAddedEvent{
@@ -35,6 +37,7 @@ func NewJWTIDPAddedEvent(
JWTEndpoint: jwtEndpoint,
KeysEndpoint: keysEndpoint,
HeaderName: headerName,
+ Audience: audience,
Options: options,
}
}
@@ -69,6 +72,7 @@ type JWTIDPChangedEvent struct {
JWTEndpoint *string `json:"jwtEndpoint,omitempty"`
KeysEndpoint *string `json:"keysEndpoint,omitempty"`
HeaderName *string `json:"headerName,omitempty"`
+ Audience *string `json:"audience,omitempty"`
OptionChanges
}
@@ -122,6 +126,12 @@ func ChangeJWTHeaderName(headerName string) func(*JWTIDPChangedEvent) {
}
}
+func ChangeJWTAudience(audience string) func(*JWTIDPChangedEvent) {
+ return func(e *JWTIDPChangedEvent) {
+ e.Audience = &audience
+ }
+}
+
func ChangeJWTOptions(options OptionChanges) func(*JWTIDPChangedEvent) {
return func(e *JWTIDPChangedEvent) {
e.OptionChanges = options
diff --git a/internal/repository/instance/idp.go b/internal/repository/instance/idp.go
index 9b9b1350f..797adc368 100644
--- a/internal/repository/instance/idp.go
+++ b/internal/repository/instance/idp.go
@@ -304,7 +304,8 @@ func NewJWTIDPAddedEvent(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options idp.Options,
) *JWTIDPAddedEvent {
@@ -321,6 +322,7 @@ func NewJWTIDPAddedEvent(
jwtEndpoint,
keysEndpoint,
headerName,
+ audience,
options,
),
}
diff --git a/internal/repository/org/idp.go b/internal/repository/org/idp.go
index 70faed4ce..bb884f047 100644
--- a/internal/repository/org/idp.go
+++ b/internal/repository/org/idp.go
@@ -304,7 +304,8 @@ func NewJWTIDPAddedEvent(
issuer,
jwtEndpoint,
keysEndpoint,
- headerName string,
+ headerName,
+ audience string,
options idp.Options,
) *JWTIDPAddedEvent {
@@ -321,6 +322,7 @@ func NewJWTIDPAddedEvent(
jwtEndpoint,
keysEndpoint,
headerName,
+ audience,
options,
),
}
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index f0e52857e..0442ee8f5 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -6542,6 +6542,13 @@ message AddJWTProviderRequest {
string keys_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string header_name = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
zitadel.idp.v1.Options provider_options = 6;
+ string audience = 7 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ description: "the expected audience in the JWT, if not provided the audience is not validated";
+ }
+ ];
}
message AddJWTProviderResponse {
@@ -6562,6 +6569,13 @@ message UpdateJWTProviderRequest {
string keys_endpoint = 5 [(validate.rules).string = {max_len: 200}];
string header_name = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
zitadel.idp.v1.Options provider_options = 7;
+ string audience = 8 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ description: "the expected audience in the JWT, if not provided the audience is not validated";
+ }
+ ];
}
message UpdateJWTProviderResponse {
diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto
index 488bde0a6..0c0fc191a 100644
--- a/proto/zitadel/idp.proto
+++ b/proto/zitadel/idp.proto
@@ -195,6 +195,13 @@ message JWTConfig {
description: "the name of the header where the JWT is sent in, default is authorization";
}
];
+ string audience = 5 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ description: "the expected audience in the JWT, if not provided the audience is not validated";
+ }
+ ];
}
message IDPIDQuery {
diff --git a/proto/zitadel/idp/v2/idp.proto b/proto/zitadel/idp/v2/idp.proto
index f3e391052..1411fd447 100644
--- a/proto/zitadel/idp/v2/idp.proto
+++ b/proto/zitadel/idp/v2/idp.proto
@@ -133,6 +133,13 @@ message JWTConfig {
"\"x-auth-token\"";
}
];
+ // The audience is the expected audience in the JWT, if not configured, the audience is not validated.
+ string audience = 5 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ }
+ ];
}
message OAuthConfig {
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index 703d294b5..ef35288b5 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -13291,6 +13291,13 @@ message AddJWTProviderRequest {
string keys_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string header_name = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
zitadel.idp.v1.Options provider_options = 6;
+ string audience = 7 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ description: "the expected audience in the JWT, if not provided the audience is not validated";
+ }
+ ];
}
message AddJWTProviderResponse {
@@ -13311,6 +13318,13 @@ message UpdateJWTProviderRequest {
string keys_endpoint = 5 [(validate.rules).string = {max_len: 200}];
string header_name = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
zitadel.idp.v1.Options provider_options = 7;
+ string audience = 8 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"my-audience\"";
+ description: "the expected audience in the JWT, if not provided the audience is not validated";
+ }
+ ];
}
message UpdateJWTProviderResponse {