feat: App API v2 (#10077)

# Which Problems Are Solved

This PR *partially* addresses #9450 . Specifically, it implements the
resource based API for the apps. APIs for app keys ARE not part of this
PR.

# How the Problems Are Solved

- `CreateApplication`, `PatchApplication` (update) and
`RegenerateClientSecret` endpoints are now unique for all app types:
API, SAML and OIDC apps.
  - All new endpoints have integration tests
  - All new endpoints are using permission checks V2

# Additional Changes

- The `ListApplications` endpoint allows to do sorting (see protobuf for
details) and filtering by app type (see protobuf).
- SAML and OIDC update endpoint can now receive requests for partial
updates

# Additional Context

Partially addresses #9450
This commit is contained in:
Marco A.
2025-06-27 17:25:44 +02:00
committed by GitHub
parent 016676e1dc
commit 2691dae2b6
48 changed files with 6845 additions and 603 deletions

View File

@@ -0,0 +1,26 @@
syntax = "proto3";
package zitadel.app.v2beta;
import "protoc-gen-openapiv2/options/annotations.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
enum APIAuthMethodType {
API_AUTH_METHOD_TYPE_BASIC = 0;
API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT = 1;
}
message APIConfig {
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334@ZITADEL\"";
description: "generated oauth2/oidc client_id";
}
];
APIAuthMethodType auth_method_type = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines how the API passes the login credentials";
}
];
}

View File

@@ -0,0 +1,94 @@
syntax = "proto3";
package zitadel.app.v2beta;
import "zitadel/app/v2beta/oidc.proto";
import "zitadel/app/v2beta/saml.proto";
import "zitadel/app/v2beta/api.proto";
import "zitadel/filter/v2/filter.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "validate/validate.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
message Application {
string id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\"";
}
];
// The timestamp of the app creation.
google.protobuf.Timestamp creation_date = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
// The timestamp of the app update.
google.protobuf.Timestamp change_date = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
AppState state = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the application";
}
];
string name = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Console\"";
}
];
oneof config {
OIDCConfig oidc_config = 6;
APIConfig api_config = 7;
SAMLConfig saml_config = 8;
}
}
enum AppState {
APP_STATE_UNSPECIFIED = 0;
APP_STATE_ACTIVE = 1;
APP_STATE_INACTIVE = 2;
APP_STATE_REMOVED = 3;
}
enum AppSorting {
APP_SORT_BY_ID = 0;
APP_SORT_BY_NAME = 1;
APP_SORT_BY_STATE = 2;
APP_SORT_BY_CREATION_DATE = 3;
APP_SORT_BY_CHANGE_DATE = 4;
}
message ApplicationSearchFilter {
oneof filter {
option (validate.required) = true;
ApplicationNameQuery name_filter = 1;
AppState state_filter = 2;
bool api_app_only = 3;
bool oidc_app_only = 4;
bool saml_app_only = 5;
}
}
message ApplicationNameQuery {
string name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Conso\""
}
];
zitadel.filter.v2.TextFilterMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used"
}
];
}

View File

@@ -0,0 +1,788 @@
syntax = "proto3";
package zitadel.app.v2beta;
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/app/v2beta/login.proto";
import "zitadel/app/v2beta/oidc.proto";
import "zitadel/app/v2beta/api.proto";
import "zitadel/app/v2beta/app.proto";
import "google/protobuf/timestamp.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/filter/v2/filter.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "Application Service";
version: "2.0-beta";
description: "This API is intended to manage apps (SAML, OIDC, etc..) in a ZITADEL instance. This service is in beta state. It can AND will continue breaking until a stable version is released.";
contact:{
name: "ZITADEL"
url: "https://zitadel.com"
email: "hi@zitadel.com"
}
license: {
name: "Apache 2.0",
url: "https://github.com/zitadel/zitadel/blob/main/LICENSING.md";
};
};
schemes: HTTPS;
schemes: HTTP;
consumes: "application/json";
consumes: "application/grpc";
produces: "application/json";
produces: "application/grpc";
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "$CUSTOM-DOMAIN";
base_path: "/";
external_docs: {
description: "Detailed information about ZITADEL",
url: "https://zitadel.com/docs"
}
security_definitions: {
security: {
key: "OAuth2";
value: {
type: TYPE_OAUTH2;
flow: FLOW_ACCESS_CODE;
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
scopes: {
scope: {
key: "openid";
value: "openid";
}
scope: {
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
}
}
}
}
}
security: {
security_requirement: {
key: "OAuth2";
value: {
scope: "openid";
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
}
}
}
responses: {
key: "403";
value: {
description: "Returned when the user does not have permission to access the resource.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
responses: {
key: "404";
value: {
description: "Returned when the resource does not exist.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
};
// Service to manage apps.
// The service provides methods to create, update, delete and list apps and app keys.
service AppService {
// Create Application
//
// Create an application. The application can be OIDC, API or SAML type, based on the input.
//
// The user needs to have project.app.write permission
//
// Required permissions:
// - project.app.write
rpc CreateApplication(CreateApplicationRequest) returns (CreateApplicationResponse) {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The created application";
}
};
};
option (google.api.http) = {
post: "/v2beta/applications"
body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Update Application
//
// Changes the configuration of an OIDC, API or SAML type application, as well as
// the application name, based on the input provided.
//
// The user needs to have project.app.write permission
//
// Required permissions:
// - project.app.write
rpc UpdateApplication(UpdateApplicationRequest) returns (UpdateApplicationResponse) {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The updated app.";
}
};
};
option (google.api.http) = {
patch: "/v2beta/applications/{id}"
body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Get Application
//
// Retrieves the application matching the provided ID.
//
// The user needs to have project.app.read permission
//
// Required permissions:
// - project.app.read
rpc GetApplication(GetApplicationRequest) returns (GetApplicationResponse) {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The fetched app.";
}
};
};
option (google.api.http) = {
get: "/v2beta/applications/{id}"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Delete Application
//
// Deletes the application belonging to the input project and matching the provided
// application ID
//
// The user needs to have project.app.delete permission
//
// Required permissions:
// - project.app.delete
rpc DeleteApplication(DeleteApplicationRequest) returns (DeleteApplicationResponse) {
option (google.api.http) = {
delete: "/v2beta/applications/{id}"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The time of deletion.";
}
};
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Deactivate Application
//
// Deactivates the application belonging to the input project and matching the provided
// application ID
//
// The user needs to have project.app.write permission
//
// Required permissions:
// - project.app.write
rpc DeactivateApplication(DeactivateApplicationRequest) returns (DeactivateApplicationResponse) {
option (google.api.http) = {
post: "/v2beta/applications/{id}/deactivate"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The time of deactivation.";
}
};
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Reactivate Application
//
// Reactivates the application belonging to the input project and matching the provided
// application ID
//
// The user needs to have project.app.write permission
//
// Required permissions:
// - project.app.write
rpc ReactivateApplication(ReactivateApplicationRequest) returns (ReactivateApplicationResponse) {
option (google.api.http) = {
post: "/v2beta/applications/{id}/reactivate"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The time of reactivation.";
}
};
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// Regenerate Client Secret
//
// Regenerates the client secret of an API or OIDC application that belongs to the input project.
//
// The user needs to have project.app.write permission
//
// Required permissions:
// - project.app.write
rpc RegenerateClientSecret(RegenerateClientSecretRequest) returns (RegenerateClientSecretResponse) {
option (google.api.http) = {
post: "/v2beta/applications/{application_id}/generate_client_secret"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The regenerated client secret.";
}
};
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
// List Applications
//
// Returns a list of applications matching the input parameters that belong to the provided
// project.
//
// The result can be sorted by app id, name, creation date, change date or state. It can also
// be filtered by app state, app type and app name.
//
// The user needs to have project.app.read permission
//
// Required permissions:
// - project.app.read
rpc ListApplications(ListApplicationsRequest) returns (ListApplicationsResponse) {
option (google.api.http) = {
post: "/v2beta/applications/search"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200";
value: {
description: "The matching applications";
}
};
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
}
}
message CreateApplicationRequest {
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string id = 2 [(validate.rules).string = {max_len: 200}];
string name = 3 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"MyApp\"";
}
];
oneof creation_request_type {
option (validate.required) = true;
CreateOIDCApplicationRequest oidc_request = 4;
CreateSAMLApplicationRequest saml_request = 5;
CreateAPIApplicationRequest api_request = 6;
}
}
message CreateApplicationResponse {
string app_id = 1;
// The timestamp of the app creation.
google.protobuf.Timestamp creation_date = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
oneof creation_response_type {
CreateOIDCApplicationResponse oidc_response = 3;
CreateSAMLApplicationResponse saml_response = 4;
CreateAPIApplicationResponse api_response = 5;
}
}
message CreateOIDCApplicationRequest {
// Callback URI of the authorization request where the code or tokens will be sent to
repeated string redirect_uris = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"http://localhost:4200/auth/callback\"]";
description: "Callback URI of the authorization request where the code or tokens will be sent to";
}
];
repeated OIDCResponseType response_types = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Determines whether a code, id_token token or just id_token will be returned"
}
];
repeated OIDCGrantType grant_types = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The flow type the application uses to gain access";
}
];
OIDCAppType app_type = 4 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Determines the paradigm of the application";
}
];
OIDCAuthMethodType auth_method_type = 5 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Defines how the application passes login credentials";
}
];
// ZITADEL will redirect to this link after a successful logout
repeated string post_logout_redirect_uris = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"http://localhost:4200/signedout\"]";
description: "ZITADEL will redirect to this link after a successful logout";
}
];
OIDCVersion version = 7 [(validate.rules).enum = {defined_only: true}];
bool dev_mode = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Used for development, some checks of the OIDC specification will not be checked.";
}
];
OIDCTokenType access_token_type = 9 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Type of the access token returned from ZITADEL";
}
];
bool access_token_role_assertion = 10 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
}
];
bool id_token_role_assertion = 11 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Adds roles to the claims of the id token even if they are not requested by scopes";
}
];
bool id_token_userinfo_assertion = 12 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
}
];
google.protobuf.Duration clock_skew = 13 [
(validate.rules).duration = {gte: {}, lte: {seconds: 5}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
example: "\"1s\"";
}
];
repeated string additional_origins = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"scheme://localhost:8080\"]";
description: "Additional origins (other than the redirect_uris) from where the API can be used, provided string has to be an origin (scheme://hostname[:port]) without path, query or fragment";
}
];
bool skip_native_app_success_page = 15 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
}
];
string back_channel_logout_uri = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://example.com/auth/backchannel\"]";
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
}
];
LoginVersion login_version = 17 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}
message CreateOIDCApplicationResponse {
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"1035496534033449\"";
description: "generated client id for this config";
}
];
string client_secret = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"gjoq34589uasgh\"";
description: "generated secret for this config";
}
];
bool none_compliant = 3;
repeated OIDCLocalizedMessage compliance_problems = 4;
}
message CreateSAMLApplicationRequest {
oneof metadata {
option (validate.required) = true;
bytes metadata_xml = 1 [(validate.rules).bytes.max_len = 500000];
string metadata_url = 2 [(validate.rules).string.max_len = 200];
}
LoginVersion login_version = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}
message CreateSAMLApplicationResponse {}
message CreateAPIApplicationRequest {
APIAuthMethodType auth_method_type = 1 [(validate.rules).enum = {defined_only: true}];
}
message CreateAPIApplicationResponse {
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"3950723409029374\"";
description: "generated secret for this config";
}
];
string client_secret = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"gjoq34589uasgh\"";
description: "generated secret for this config";
}
];
}
message UpdateApplicationRequest {
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string id = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"45984352431\"";
}
];
string name = 3 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"MyApplicationName\"";
min_length: 1;
max_length: 200;
}
];
oneof update_request_type {
UpdateSAMLApplicationConfigurationRequest saml_configuration_request = 4;
UpdateOIDCApplicationConfigurationRequest oidc_configuration_request = 5;
UpdateAPIApplicationConfigurationRequest api_configuration_request = 6;
}
}
message UpdateApplicationResponse {
// The timestamp of the app update.
google.protobuf.Timestamp change_date = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
}
message UpdateSAMLApplicationConfigurationRequest {
oneof metadata {
option (validate.required) = true;
bytes metadata_xml = 1 [(validate.rules).bytes.max_len = 500000];
string metadata_url = 2 [(validate.rules).string.max_len = 200];
}
optional LoginVersion login_version = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}
message UpdateOIDCApplicationConfigurationRequest {
repeated string redirect_uris = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"http://localhost:4200/auth/callback\"]";
description: "Callback URI of the authorization request where the code or tokens will be sent to";
}
];
repeated OIDCResponseType response_types = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Determines whether a code, id_token token or just id_token will be returned"
}
];
repeated OIDCGrantType grant_types = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The flow type the application uses to gain access";
}
];
optional OIDCAppType app_type = 4 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Determines the paradigm of the application";
}
];
optional OIDCAuthMethodType auth_method_type = 5 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Defines how the application passes login credentials";
}
];
repeated string post_logout_redirect_uris = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"http://localhost:4200/signedout\"]";
description: "ZITADEL will redirect to this link after a successful logout";
}
];
optional OIDCVersion version = 7 [(validate.rules).enum = {defined_only: true}];
optional bool dev_mode = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Used for development, some checks of the OIDC specification will not be checked.";
}
];
optional OIDCTokenType access_token_type = 9 [
(validate.rules).enum = {defined_only: true},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Type of the access token returned from ZITADEL";
}
];
optional bool access_token_role_assertion = 10 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
}
];
optional bool id_token_role_assertion = 11 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Adds roles to the claims of the id token even if they are not requested by scopes";
}
];
optional bool id_token_userinfo_assertion = 12 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
}
];
optional google.protobuf.Duration clock_skew = 13 [
(validate.rules).duration = {gte: {}, lte: {seconds: 5}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
example: "\"1s\"";
}
];
repeated string additional_origins = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"scheme://localhost:8080\"]";
description: "Additional origins (other than the redirect_uris) from where the API can be used, provided string has to be an origin (scheme://hostname[:port]) without path, query or fragment";
}
];
optional bool skip_native_app_success_page = 15 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
}
];
optional string back_channel_logout_uri = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://example.com/auth/backchannel\"]";
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
}
];
optional LoginVersion login_version = 17 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}
message UpdateAPIApplicationConfigurationRequest {
APIAuthMethodType auth_method_type = 1 [(validate.rules).enum = {defined_only: true}];
}
message GetApplicationRequest {
string id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"45984352431\"";
}
];
}
message GetApplicationResponse {
Application app = 1;
}
message DeleteApplicationRequest {
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message DeleteApplicationResponse {
google.protobuf.Timestamp deletion_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
}
message DeactivateApplicationRequest{
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message DeactivateApplicationResponse{
google.protobuf.Timestamp deactivation_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
}
message ReactivateApplicationRequest{
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message ReactivateApplicationResponse{
google.protobuf.Timestamp reactivation_date = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
}
message RegenerateClientSecretRequest{
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string application_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
oneof app_type {
option (validate.required) = true;
bool is_oidc = 3;
bool is_api = 4;
}
}
message RegenerateClientSecretResponse{
string client_secret = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"gjoq34589uasgh\"";
description: "generated secret for the client";
}
];
// The timestamp of the creation of the new client secret
google.protobuf.Timestamp creation_date = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
}
message ListApplicationsRequest {
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
// Pagination and sorting.
zitadel.filter.v2.PaginationRequest pagination = 2;
//criteria the client is looking for
repeated ApplicationSearchFilter filters = 3;
AppSorting sorting_column = 4;
}
message ListApplicationsResponse {
repeated Application applications = 1;
// Contains the total number of apps matching the query and the applied limit.
zitadel.filter.v2.PaginationResponse pagination = 2;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package zitadel.app.v2beta;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
message LoginVersion {
oneof version {
LoginV1 login_v1 = 1;
LoginV2 login_v2 = 2;
}
}
message LoginV1 {}
message LoginV2 {
// Optionally specify a base uri of the login UI. If unspecified the default URI will be used.
optional string base_uri = 1;
}

View File

@@ -0,0 +1,166 @@
syntax = "proto3";
package zitadel.app.v2beta;
import "zitadel/app/v2beta/login.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "google/protobuf/duration.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
message OIDCLocalizedMessage {
string key = 1;
string localized_message = 2;
}
enum OIDCResponseType {
OIDC_RESPONSE_TYPE_UNSPECIFIED = 0;
OIDC_RESPONSE_TYPE_CODE = 1;
OIDC_RESPONSE_TYPE_ID_TOKEN = 2;
OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN = 3;
}
enum OIDCGrantType{
OIDC_GRANT_TYPE_AUTHORIZATION_CODE = 0;
OIDC_GRANT_TYPE_IMPLICIT = 1;
OIDC_GRANT_TYPE_REFRESH_TOKEN = 2;
OIDC_GRANT_TYPE_DEVICE_CODE = 3;
OIDC_GRANT_TYPE_TOKEN_EXCHANGE = 4;
}
enum OIDCAppType {
OIDC_APP_TYPE_WEB = 0;
OIDC_APP_TYPE_USER_AGENT = 1;
OIDC_APP_TYPE_NATIVE = 2;
}
enum OIDCAuthMethodType {
OIDC_AUTH_METHOD_TYPE_BASIC = 0;
OIDC_AUTH_METHOD_TYPE_POST = 1;
OIDC_AUTH_METHOD_TYPE_NONE = 2;
OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT = 3;
}
enum OIDCVersion {
OIDC_VERSION_1_0 = 0;
}
enum OIDCTokenType {
OIDC_TOKEN_TYPE_BEARER = 0;
OIDC_TOKEN_TYPE_JWT = 1;
}
message OIDCConfig {
repeated string redirect_uris = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://console.zitadel.ch/auth/callback\"]";
description: "Callback URI of the authorization request where the code or tokens will be sent to";
}
];
repeated OIDCResponseType response_types = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Determines whether a code, id_token token or just id_token will be returned"
}
];
repeated OIDCGrantType grant_types = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The flow type the application uses to gain access";
}
];
OIDCAppType app_type = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "determines the paradigm of the application";
}
];
string client_id = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334@ZITADEL\"";
description: "generated oauth2/oidc client id";
}
];
OIDCAuthMethodType auth_method_type = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines how the application passes login credentials";
}
];
repeated string post_logout_redirect_uris = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://console.zitadel.ch/logout\"]";
description: "ZITADEL will redirect to this link after a successful logout";
}
];
OIDCVersion version = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the OIDC version used by the application";
}
];
bool none_compliant = 9 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "specifies whether the config is OIDC compliant. A production configuration SHOULD be compliant";
}
];
repeated OIDCLocalizedMessage compliance_problems = 10 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "lists the problems for non-compliancy";
}
];
bool dev_mode = 11 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "used for development";
}
];
OIDCTokenType access_token_type = 12 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "type of the access token returned from ZITADEL";
}
];
bool access_token_role_assertion = 13 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
}
];
bool id_token_role_assertion = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "adds roles to the claims of the id token even if they are not requested by scopes";
}
];
bool id_token_userinfo_assertion = 15 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
}
];
google.protobuf.Duration clock_skew = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
// min: "0s";
// max: "5s";
}
];
repeated string additional_origins = 17 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://console.zitadel.ch/auth/callback\"]";
description: "additional origins (other than the redirect_uris) from where the API can be used";
}
];
repeated string allowed_origins = 18 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://console.zitadel.ch/auth/callback\"]";
description: "all allowed origins from where the API can be used";
}
];
bool skip_native_app_success_page = 19 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
}
];
string back_channel_logout_uri = 20 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://example.com/auth/backchannel\"]";
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
}
];
LoginVersion login_version = 21 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package zitadel.app.v2beta;
import "zitadel/app/v2beta/login.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
message SAMLConfig {
oneof metadata{
bytes metadata_xml = 1;
string metadata_url = 2;
}
LoginVersion login_version = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
}
];
}

View File

@@ -3287,6 +3287,7 @@ service ManagementService {
};
}
// Deprecated: Use [GetApplication](/apis/resources/application_service_v2/application-service-get-application.api.mdx) instead to fetch an app
rpc GetAppByID(GetAppByIDRequest) returns (GetAppByIDResponse) {
option (google.api.http) = {
get: "/projects/{project_id}/apps/{app_id}"
@@ -3309,9 +3310,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [ListApplications](/apis/resources/application_service_v2/application-service-list-applications.api.mdx) instead to list applications
rpc ListApps(ListAppsRequest) returns (ListAppsResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/_search"
@@ -3335,6 +3338,7 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
@@ -3363,6 +3367,7 @@ service ManagementService {
};
}
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create an OIDC application
rpc AddOIDCApp(AddOIDCAppRequest) returns (AddOIDCAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/oidc"
@@ -3386,62 +3391,74 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
rpc AddSAMLApp(AddSAMLAppRequest) returns (AddSAMLAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/saml"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Create Application (SAML)";
description: "Create a new SAML client. Returns an entity ID"
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
};
}
rpc AddAPIApp(AddAPIAppRequest) returns (AddAPIAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/api"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create a SAML application
rpc AddSAMLApp(AddSAMLAppRequest) returns (AddSAMLAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/saml"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Create Application (API)";
description: "Create a new API client. The client id will be generated and returned in the response. Depending on the chosen configuration also a secret will be generated and returned."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Create Application (SAML)";
description: "Create a new SAML client. Returns an entity ID"
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
deprecated: true;
};
}
// Create Application (API)
//
// Create a new API client. The client id will be generated and returned in the response.
// Depending on the chosen configuration also a secret will be generated and returned.
//
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create an API application
rpc AddAPIApp(AddAPIAppRequest) returns (AddAPIAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/api"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Create Application (API)";
description: "Create a new API client. The client id will be generated and returned in the response. Depending on the chosen configuration also a secret will be generated and returned."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
deprecated: true;
};
}
// Changes application
//
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the generic params of an app
rpc UpdateApp(UpdateAppRequest) returns (UpdateAppResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}"
@@ -3465,9 +3482,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of an OIDC app
rpc UpdateOIDCAppConfig(UpdateOIDCAppConfigRequest) returns (UpdateOIDCAppConfigResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}/oidc_config"
@@ -3491,61 +3510,67 @@ service ManagementService {
required: false;
};
};
deprecated: true
};
}
rpc UpdateSAMLAppConfig(UpdateSAMLAppConfigRequest) returns (UpdateSAMLAppConfigResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}/saml_config"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Update SAML Application Config";
description: "Update the SAML specific configuration of an application."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
};
}
rpc UpdateAPIAppConfig(UpdateAPIAppConfigRequest) returns (UpdateAPIAppConfigResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}/api_config"
body: "*"
};
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of a SAML app
rpc UpdateSAMLAppConfig(UpdateSAMLAppConfigRequest) returns (UpdateSAMLAppConfigResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}/saml_config"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Update API Application Config";
description: "Update the OIDC-specific configuration of an application."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Update SAML Application Config";
description: "Update the SAML specific configuration of an application."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of an API app
rpc UpdateAPIAppConfig(UpdateAPIAppConfigRequest) returns (UpdateAPIAppConfigResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/apps/{app_id}/api_config"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "project.app.write"
check_field_name: "ProjectId"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Applications";
summary: "Update API Application Config";
description: "Update the OIDC-specific configuration of an application."
parameters: {
headers: {
name: "x-zitadel-orgid";
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
type: STRING,
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [DeactivateApplication](/apis/resources/application_service_v2/application-service-deactivate-application.api.mdx) instead to deactivate an app
rpc DeactivateApp(DeactivateAppRequest) returns (DeactivateAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/{app_id}/_deactivate"
@@ -3569,9 +3594,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [ReactivateApplication](/apis/resources/application_service_v2/application-service-reactivate-application.api.mdx) instead to reactivate an app
rpc ReactivateApp(ReactivateAppRequest) returns (ReactivateAppResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/{app_id}/_reactivate"
@@ -3595,9 +3622,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [DeleteApplication](/apis/resources/application_service_v2/application-service-delete-application.api.mdx) instead to delete an app
rpc RemoveApp(RemoveAppRequest) returns (RemoveAppResponse) {
option (google.api.http) = {
delete: "/projects/{project_id}/apps/{app_id}"
@@ -3620,9 +3649,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [RegenerateClientSecret](/apis/resources/application_service_v2/application-service-regenerate-client-secret.api.mdx) instead to regenerate an OIDC app client secret
rpc RegenerateOIDCClientSecret(RegenerateOIDCClientSecretRequest) returns (RegenerateOIDCClientSecretResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/{app_id}/oidc_config/_generate_client_secret"
@@ -3646,9 +3677,11 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}
// Deprecated: Use [RegenerateClientSecret](/apis/resources/application_service_v2/application-service-regenerate-client-secret.api.mdx) instead to regenerate an API app client secret
rpc RegenerateAPIClientSecret(RegenerateAPIClientSecretRequest) returns (RegenerateAPIClientSecretResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/apps/{app_id}/api_config/_generate_client_secret"
@@ -3672,6 +3705,7 @@ service ManagementService {
required: false;
};
};
deprecated: true;
};
}