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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions api/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ components:
example: "770e8400-e29b-41d4-a716-446655440002"
type:
type: string
enum: [browser, fullstack, mobile, m2m, custom]
enum: [browser, fullstack, mobile, m2m, mcp, custom]
description: >
The canonical application type (platform/client class). Required at creation and
immutable thereafter. Use custom for applications that do not fit the other classes.
Expand Down Expand Up @@ -878,10 +878,11 @@ components:
type: string
description: >
Flow Secret used to authenticate when initiating a flow directly via the Flow Execution API.
Issued only to full-stack and custom applications that either have no OAuth 2.0 configuration
(embedded) or are configured as a confidential, non-redirect OAuth 2.0 client. Browser, mobile,
and machine-to-machine applications are never issued one. When omitted on creation, a secret is
generated automatically for eligible applications and returned once in the creation response.
Issued only to full-stack, custom, and mcp applications that either have no OAuth 2.0
configuration (embedded) or are configured as a confidential, non-redirect OAuth 2.0 client.
Browser, mobile, and machine-to-machine applications are never issued one. When omitted
on creation, a secret is generated automatically for eligible applications and returned once in
the creation response.
example: "550e8400e29b41d4a716446655440000abcdef0123456789"

ApplicationCompleteResponse:
Expand Down Expand Up @@ -939,7 +940,7 @@ components:
example: "660e8400-e29b-41d4-a716-446655440001"
type:
type: string
enum: [browser, fullstack, mobile, m2m, custom]
enum: [browser, fullstack, mobile, m2m, mcp, custom]
description: >
The canonical application type (platform/client class). Required at creation and
immutable thereafter. Use custom for applications that do not fit the other classes.
Expand All @@ -952,10 +953,10 @@ components:
type: string
description: >
Flow Secret used to authenticate when initiating a flow directly via the Flow Execution API.
Issued and returned once in the creation response only for full-stack and custom applications
that either have no OAuth 2.0 configuration (embedded) or are configured as a confidential,
non-redirect OAuth 2.0 client. Browser, mobile, and machine-to-machine applications are never
issued one.
Issued and returned once in the creation response only for full-stack, custom, and mcp
applications that either have no OAuth 2.0 configuration (embedded) or are configured as a
confidential, non-redirect OAuth 2.0 client. Browser, mobile, and machine-to-machine
applications are never issued one.
example: "550e8400e29b41d4a716446655440000abcdef0123456789"
url:
type: string
Expand Down Expand Up @@ -1075,7 +1076,7 @@ components:
example: "770e8400-e29b-41d4-a716-446655440002"
type:
type: string
enum: [browser, fullstack, mobile, m2m, custom]
enum: [browser, fullstack, mobile, m2m, mcp, custom]
description: >
The canonical application type (platform/client class). Required at creation and
immutable thereafter. Use custom for applications that do not fit the other classes.
Expand Down Expand Up @@ -1203,7 +1204,7 @@ components:
example: "770e8400-e29b-41d4-a716-446655440002"
type:
type: string
enum: [browser, fullstack, mobile, m2m, custom]
enum: [browser, fullstack, mobile, m2m, mcp, custom]
description: >
The canonical application type (platform/client class). Required at creation and
immutable thereafter. Use custom for applications that do not fit the other classes.
Expand Down
8 changes: 5 additions & 3 deletions backend/internal/application/application_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,10 @@ func (s *ApplicationTypeTestSuite) TestFlowSecretIneligibleByType() {
}
}

// TestFullStackAndCustomFlowSecretEligibility verifies full-stack and custom apps derive eligibility
// from the OAuth config shape: only confidential, non-redirect (embedded) clients are eligible.
func (s *ApplicationTypeTestSuite) TestFullStackAndCustomFlowSecretEligibility() {
// TestFullStackCustomAndMCPFlowSecretEligibility verifies full-stack, custom, and mcp apps derive
// eligibility from the OAuth config shape: an app with no OAuth configuration (embedded), or a
// confidential, non-redirect client, is eligible; a redirect or m2m-shaped client is not.
func (s *ApplicationTypeTestSuite) TestFullStackCustomAndMCPFlowSecretEligibility() {
embedded := &providers.InboundAuthConfigWithSecret{
Type: providers.OAuthInboundAuthType,
OAuthConfig: &providers.OAuthConfigWithSecret{
Expand All @@ -145,6 +146,7 @@ func (s *ApplicationTypeTestSuite) TestFullStackAndCustomFlowSecretEligibility()
for _, appType := range []model.ApplicationType{
model.ApplicationTypeFullStack,
model.ApplicationTypeCustom,
model.ApplicationTypeMCP,
} {
// Embedded app with no OAuth config, and confidential non-redirect app, are eligible.
s.True(isFlowSecretEligible(appType, nil), "type %q embedded should be eligible", appType)
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/application/error_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ var (
ErrorDescription: tidcommon.I18nMessage{
Key: "error.applicationservice.invalid_application_type_description",
DefaultValue: "The provided application type is not supported. It must be one of: " +
"browser, fullstack, mobile, m2m, custom.",
"browser, fullstack, mobile, m2m, mcp, custom.",
},
}
// ErrorApplicationTypeImmutable is returned when an update attempts to change the application type.
Expand All @@ -559,7 +559,7 @@ var (
ErrorDescription: tidcommon.I18nMessage{
Key: "error.applicationservice.application_type_required_description",
DefaultValue: "An application type must be provided. It must be one of: " +
"browser, fullstack, mobile, m2m, custom.",
"browser, fullstack, mobile, m2m, mcp, custom.",
},
}
)
6 changes: 3 additions & 3 deletions backend/internal/application/model/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ type ApplicationDTO struct {
OUHandle string `json:"ouHandle,omitempty" jsonschema:"Organization unit handle. Resolved to an ID by the service layer."`
Name string `json:"name" jsonschema:"Application name."`
Description string `json:"description,omitempty" jsonschema:"Optional description of the application's purpose or functionality."`
Type ApplicationType `json:"type,omitempty" jsonschema:"Application type. Canonical platform/client class: browser, fullstack, mobile, m2m, or custom. Required at creation and immutable."`
Type ApplicationType `json:"type,omitempty" jsonschema:"Application type. Canonical platform/client class: browser, fullstack, mobile, m2m, mcp, or custom. Required at creation and immutable."`
Template string `json:"template,omitempty" jsonschema:"Application template. Optional. Display metadata identifying the frontend template used to create the application."`
FlowSecret string `json:"flowSecret,omitempty" jsonschema:"Flow Secret used to authenticate when initiating a flow directly via the Flow Execution API. Issued once on creation only to full-stack and custom applications that either have no OAuth 2.0 configuration (embedded) or are configured as a confidential, non-redirect OAuth 2.0 client. Browser, mobile, and m2m applications are never issued one."`
FlowSecret string `json:"flowSecret,omitempty" jsonschema:"Flow Secret used to authenticate when initiating a flow directly via the Flow Execution API. Issued once on creation only to full-stack, custom, and mcp applications that either have no OAuth 2.0 configuration (embedded) or are configured as a confidential, non-redirect OAuth 2.0 client. Browser, mobile, and m2m applications are never issued one."`

URL string `json:"url,omitempty" jsonschema:"Application home URL. Optional. The main URL where your application is hosted."`
LogoURL string `json:"logoUrl,omitempty" jsonschema:"Logo image URL. Optional. Displayed in login pages and application listings."`
Expand Down Expand Up @@ -187,7 +187,7 @@ type BasicApplicationResponse struct {
IsSignOutFlowEnabled bool `json:"isSignOutFlowEnabled" jsonschema:"Sign-out enabled status."`
ThemeID string `json:"themeId,omitempty" jsonschema:"Theme ID."`
LayoutID string `json:"layoutId,omitempty" jsonschema:"Layout ID."`
Type ApplicationType `json:"type,omitempty" jsonschema:"Application type (browser, fullstack, mobile, m2m, custom)."`
Type ApplicationType `json:"type,omitempty" jsonschema:"Application type (browser, fullstack, mobile, m2m, mcp, custom)."`
Template string `json:"template,omitempty" jsonschema:"Application Template."`
IsReadOnly bool `json:"isReadOnly" jsonschema:"Indicates if the application is read-only (declarative/immutable)."`
}
Expand Down
1 change: 1 addition & 0 deletions backend/internal/application/model/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
ApplicationTypeFullStack ApplicationType = "fullstack"
ApplicationTypeMobile ApplicationType = "mobile"
ApplicationTypeM2M ApplicationType = "m2m"
ApplicationTypeMCP ApplicationType = "mcp"
ApplicationTypeCustom ApplicationType = "custom"
)

Expand Down
2 changes: 1 addition & 1 deletion backend/internal/application/model/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ package model
func IsValidApplicationType(t ApplicationType) bool {
switch t {
case ApplicationTypeBrowser, ApplicationTypeFullStack, ApplicationTypeMobile,
ApplicationTypeM2M, ApplicationTypeCustom:
ApplicationTypeM2M, ApplicationTypeMCP, ApplicationTypeCustom:
return true
default:
return false
Expand Down
9 changes: 5 additions & 4 deletions backend/internal/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,9 @@ func (as *applicationService) updateEntityDataForApplicationUpdate(ctx context.C
}

// isFlowSecretEligible reports whether an application may hold a Flow Secret. Browser (public
// redirect), mobile (attestation), and m2m (direct token) apps never hold one. Full-stack and custom
// apps derive eligibility from the OAuth config shape: only confidential, non-redirect clients (or
// embedded apps with no OAuth config) are eligible.
// redirect), mobile (attestation), and m2m (direct token) apps never hold one. Full-stack, custom,
// and mcp apps derive eligibility from the OAuth config shape: only confidential, non-redirect
// clients (or embedded apps with no OAuth config) are eligible.
func isFlowSecretEligible(appType model.ApplicationType,
inboundAuthConfig *providers.InboundAuthConfigWithSecret) bool {
switch appType {
Expand Down Expand Up @@ -1176,7 +1176,8 @@ func (as *applicationService) validateApplicationFields(
// immutability (update) are handled by the respective callers.
switch app.Type {
case "", model.ApplicationTypeBrowser, model.ApplicationTypeFullStack,
model.ApplicationTypeMobile, model.ApplicationTypeM2M, model.ApplicationTypeCustom:
model.ApplicationTypeMobile, model.ApplicationTypeM2M, model.ApplicationTypeMCP,
model.ApplicationTypeCustom:
default:
return &ErrorInvalidApplicationType
}
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/flow/flowexec/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func (s *flowExecService) resolveFlowInitiationMode(
}
return flowInitiationAttestation, client.Attestation, nil
default:
// Full-stack and custom apps may be embedded or redirect-based; derive from the OAuth profile.
// Full-stack, custom, and mcp apps may be embedded or redirect-based; derive from the OAuth profile.
return s.resolveFlowInitiationModeFromProfile(ctx, appID)
}
}
Expand Down
17 changes: 14 additions & 3 deletions backend/internal/flow/flowexec/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2417,7 +2417,7 @@ func (s *ServiceTestSuite) TestSetApplicationToContext_BuildApplicationError() {

// The flow-initiation mode is resolved from the application type. Machine-to-machine, browser, and
// mobile (without attestation) apps may not initiate a flow directly and never consult the OAuth
// profile. Full-stack and custom apps derive the mode from their profile.
// profile. Full-stack, custom, and mcp apps derive the mode from their profile.
func (s *ServiceTestSuite) TestResolveFlowInitiationMode_ByType() {
const appID = "test-app"
tokenExchange := string(providers.GrantTypeTokenExchange)
Expand Down Expand Up @@ -2445,6 +2445,16 @@ func (s *ServiceTestSuite) TestResolveFlowInitiationMode_ByType() {
attestation: &providers.AttestationConfig{Apple: &providers.AppleAttestationConfig{}},
expectMode: flowInitiationAttestation,
},
{
name: "mcp embedded uses flow secret", appType: model.ApplicationTypeMCP,
profile: &providers.OAuthProfile{GrantTypes: []string{"client_credentials", tokenExchange}},
expectMode: flowInitiationFlowSecret,
},
{
name: "mcp redirect not permitted", appType: model.ApplicationTypeMCP,
profile: &providers.OAuthProfile{GrantTypes: []string{"authorization_code"}},
expectMode: flowInitiationNotPermitted,
},
{
name: "fullstack redirect not permitted", appType: model.ApplicationTypeFullStack,
profile: &providers.OAuthProfile{GrantTypes: []string{"authorization_code"}},
Expand Down Expand Up @@ -2489,8 +2499,9 @@ func (s *ServiceTestSuite) TestResolveFlowInitiationMode_ByType() {
}
mockActorProvider.EXPECT().GetInboundClientByID(mock.Anything, appID).Return(client, nil)

// The OAuth profile is consulted for full-stack and custom apps.
if tc.appType == model.ApplicationTypeFullStack || tc.appType == model.ApplicationTypeCustom {
// The OAuth profile is consulted for full-stack, custom, and mcp apps.
if tc.appType == model.ApplicationTypeFullStack || tc.appType == model.ApplicationTypeCustom ||
tc.appType == model.ApplicationTypeMCP {
mockActorProvider.EXPECT().GetOAuthProfileByID(mock.Anything, appID).Return(tc.profile, tc.profileErr)
}

Expand Down
4 changes: 2 additions & 2 deletions backend/internal/system/i18n/core/defaults.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/pkg/thunderidengine/providers/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ type Application struct {
OUID string `yaml:"ouId,omitempty" json:"ouId,omitempty" jsonschema:"Organization unit ID. The OU this application belongs to."`
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"Application name."`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"Optional description of the application's purpose."`
Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"Application type (browser, fullstack, mobile, m2m, custom)."`
Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"Application type (browser, fullstack, mobile, m2m, mcp, custom)."`
Template string `yaml:"template,omitempty" json:"template,omitempty" jsonschema:"Template used to create the application."`

URL string `yaml:"url,omitempty" json:"url,omitempty" jsonschema:"Application home URL."`
Expand Down
6 changes: 3 additions & 3 deletions docs/content/guides/applications/application-settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ On the **Customization** tab, control how your application looks and what legal
## Configure What Goes Into Tokens

:::note
This section applies to OAuth 2.0 / OIDC applications (Browser App, Full-stack App, Mobile App, and Backend Service). For applications that use the embedded sign-in approach, user attributes are configured in the assertion returned by the flow, not in tokens.
This section applies to OAuth 2.0 / OIDC applications (Browser App, Full-stack App, Mobile App, Backend Service, MCP Client, and Custom App). For applications that use the embedded sign-in approach, user attributes are configured in the assertion returned by the flow, not in tokens.
:::

On the **Token** tab, configure what your application's tokens carry and how long they stay valid. The tab has two sub-tabs:
Expand Down Expand Up @@ -113,7 +113,7 @@ On the **Application** sub-tab, configure the access token the application recei
## Rotate the Client Secret

:::note
Client secrets are only present on confidential clients, Full-stack App and Backend Service application types. This section does not apply to public clients such as Browser App or Mobile App.
Client secrets are only present on confidential clients: Full-stack App and Backend Service application types, and MCP Client or Custom App applications configured as confidential. This section does not apply to public clients such as Browser App, Mobile App, or an MCP Client or Custom App configured as public.
:::

If you need to invalidate the current client secret, open the General tab and click **Regenerate Client Secret** in the **Danger Zone**.
Expand All @@ -131,7 +131,7 @@ This section applies to OAuth 2.0 / OIDC applications only.
The **Advanced Settings** tab shows the OAuth 2.0 settings configured at creation.
| Setting | Description |
|---------|-------------|
| **Grant Types** | The OAuth 2.0 flows this application can use. Supported values: `authorization_code`, `refresh_token`, `client_credentials`, `urn:ietf:params:oauth:grant-type:token-exchange`, `urn:ietf:params:oauth:grant-type:jwt-bearer`. Whether this application can initiate flows directly via `POST /flow/execute` depends on its [application type](../manage-applications#application-types) and, for Full-stack and Custom applications, its grant configuration. See [Direct Initiation Restriction](../../key-concepts/authentication/integration-models.mdx#app-native) for the full rule. |
| **Grant Types** | The OAuth 2.0 flows this application can use. Supported values: `authorization_code`, `refresh_token`, `client_credentials`, `urn:ietf:params:oauth:grant-type:token-exchange`, `urn:ietf:params:oauth:grant-type:jwt-bearer`. Whether this application can initiate flows directly via `POST /flow/execute` depends on its [application type](../manage-applications#application-types) and, for Full-stack, Custom, and MCP Client applications, its grant configuration. See [Direct Initiation Restriction](../../key-concepts/authentication/integration-models.mdx#app-native) for the full rule. |
| **Response Types** | The response types the application can request (for example, `code`). |
| **Token Endpoint Auth Method** | How the application authenticates at the token endpoint: `client_secret_basic` (default), `client_secret_post`, `private_key_jwt`, or `none` (public clients). |
| **Public Client** | Whether this is a public client that cannot securely store a client secret. |
Expand Down
Loading
Loading