From 69bb9102eab65d1fafcad2ee6147b25da463d3b9 Mon Sep 17 00:00:00 2001 From: Florin Chelaru Date: Tue, 2 Sep 2025 16:50:51 +0300 Subject: [PATCH 1/2] feat: add OIDC provider for Okta --- .schemastore/config.schema.json | 5 +- embedx/config.schema.json | 5 +- selfservice/strategy/oidc/provider_config.go | 2 + selfservice/strategy/oidc/provider_okta.go | 76 +++++++++++++++++++ .../strategy/oidc/provider_okta_test.go | 35 +++++++++ 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 selfservice/strategy/oidc/provider_okta.go create mode 100644 selfservice/strategy/oidc/provider_okta_test.go diff --git a/.schemastore/config.schema.json b/.schemastore/config.schema.json index 3a8297d9d1c7..8fd5db457b33 100644 --- a/.schemastore/config.schema.json +++ b/.schemastore/config.schema.json @@ -432,7 +432,7 @@ }, "provider": { "title": "Provider", - "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon.", + "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, okta.", "type": "string", "enum": [ "github", @@ -456,7 +456,8 @@ "linkedin", "linkedin_v2", "lark", - "x" + "x", + "okta" ], "examples": ["google"] }, diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 4b9d5e26db5d..2f32a21fc993 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -436,7 +436,7 @@ }, "provider": { "title": "Provider", - "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon.", + "description": "Can be one of github, github-app, gitlab, generic, google, microsoft, discord, salesforce, slack, facebook, auth0, vk, yandex, apple, spotify, netid, dingtalk, patreon, okta.", "type": "string", "enum": [ "github", @@ -462,7 +462,8 @@ "linkedin_v2", "lark", "x", - "fedcm-test" + "fedcm-test", + "okta" ], "examples": ["google"] }, diff --git a/selfservice/strategy/oidc/provider_config.go b/selfservice/strategy/oidc/provider_config.go index 0398b3813885..b25e8edceec0 100644 --- a/selfservice/strategy/oidc/provider_config.go +++ b/selfservice/strategy/oidc/provider_config.go @@ -39,6 +39,7 @@ type Configuration struct { // - dingtalk // - linkedin // - patreon + // - okta Provider string `json:"provider"` // Label represents an optional label which can be used in the UI generation. @@ -189,6 +190,7 @@ var supportedProviders = map[string]func(config *Configuration, reg Dependencies "line": NewProviderLineV21, "jackson": NewProviderJackson, "fedcm-test": NewProviderTestFedcm, + "okta": NewProviderOkta, } func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) { diff --git a/selfservice/strategy/oidc/provider_okta.go b/selfservice/strategy/oidc/provider_okta.go new file mode 100644 index 000000000000..5080a738eb2a --- /dev/null +++ b/selfservice/strategy/oidc/provider_okta.go @@ -0,0 +1,76 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oidc + +import ( + "context" + + gooidc "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" + + "github.com/ory/x/stringslice" +) + +type ProviderOkta struct { + *ProviderGenericOIDC + JWKSUrl string +} + +func NewProviderOkta( + config *Configuration, + reg Dependencies, +) Provider { + return &ProviderOkta{ + ProviderGenericOIDC: &ProviderGenericOIDC{ + config: config, + reg: reg, + }, + JWKSUrl: config.IssuerURL + "/oauth2/v1/keys", + } +} + +func (o *ProviderOkta) oauth2ConfigFromEndpoint(ctx context.Context, endpoint oauth2.Endpoint) *oauth2.Config { + scope := o.config.Scope + if !stringslice.Has(scope, gooidc.ScopeOpenID) { + scope = append(scope, gooidc.ScopeOpenID) + } + + return &oauth2.Config{ + ClientID: o.config.ClientID, + ClientSecret: o.config.ClientSecret, + Endpoint: endpoint, + Scopes: scope, + RedirectURL: o.config.Redir(o.reg.Config().OIDCRedirectURIBase(ctx)), + } +} + +func (o *ProviderOkta) OAuth2(ctx context.Context) (*oauth2.Config, error) { + p, err := o.provider(ctx) + if err != nil { + return nil, err + } + + endpoint := p.Endpoint() + return o.oauth2ConfigFromEndpoint(ctx, endpoint), nil +} + +func (o *ProviderOkta) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { + options := o.ProviderGenericOIDC.AuthCodeURLOptions(r) + return options +} + +var _ IDTokenVerifier = new(ProviderOkta) + +func (p *ProviderOkta) Verify(ctx context.Context, rawIDToken string) (*Claims, error) { + keySet := gooidc.NewRemoteKeySet(ctx, p.JWKSUrl) + ctx = gooidc.ClientContext(ctx, p.reg.HTTPClient(ctx).HTTPClient) + return verifyToken(ctx, keySet, p.config, rawIDToken, p.config.IssuerURL) +} + +var _ NonceValidationSkipper = new(ProviderOkta) + +func (a *ProviderOkta) CanSkipNonce(c *Claims) bool { + // Not all SDKs support nonce validation, so we skip it if no nonce is present in the claims of the ID Token. + return c.Nonce == "" +} diff --git a/selfservice/strategy/oidc/provider_okta_test.go b/selfservice/strategy/oidc/provider_okta_test.go new file mode 100644 index 000000000000..94538914eb7d --- /dev/null +++ b/selfservice/strategy/oidc/provider_okta_test.go @@ -0,0 +1,35 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oidc_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ory/kratos/internal" + "github.com/ory/kratos/selfservice/strategy/oidc" +) + +func TestProviderWorkOS(t *testing.T) { + _, reg := internal.NewVeryFastRegistryWithoutDB(t) + + p := oidc.NewProviderOkta(&oidc.Configuration{ + Provider: "okta", + ID: "test-okta-id", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + IssuerURL: "https://foo.okta.com", + Mapper: "file://./stub/oidc.facebook.jsonnet", + RequestedClaims: nil, + Scope: []string{}, + }, reg) + + c, err := p.(oidc.OAuth2Provider).OAuth2(context.Background()) + require.NoError(t, err) + assert.Equal(t, "https://foo.okta.com/oauth2/v1/token", c.Endpoint.TokenURL) + assert.Equal(t, "https://foo.okta.com/oauth2/v1/authorize", c.Endpoint.AuthURL) +} From 179896febc7292e4a6503e1e89c932f9e831e0d1 Mon Sep 17 00:00:00 2001 From: Florin Chelaru Date: Mon, 8 Sep 2025 12:50:03 +0300 Subject: [PATCH 2/2] add id token verification for microsoft oidc provider --- selfservice/strategy/oidc/provider_microsoft.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/selfservice/strategy/oidc/provider_microsoft.go b/selfservice/strategy/oidc/provider_microsoft.go index 408a11096573..289e0930853b 100644 --- a/selfservice/strategy/oidc/provider_microsoft.go +++ b/selfservice/strategy/oidc/provider_microsoft.go @@ -24,6 +24,7 @@ var _ OAuth2Provider = (*ProviderMicrosoft)(nil) type ProviderMicrosoft struct { *ProviderGenericOIDC + JWKSUrl string } func NewProviderMicrosoft( @@ -35,6 +36,7 @@ func NewProviderMicrosoft( config: config, reg: reg, }, + JWKSUrl: "https://login.microsoftonline.com/" + config.Tenant + "/discovery/v2.0/keys", } } @@ -130,3 +132,11 @@ type microsoftUnverifiedClaims struct { func (c *microsoftUnverifiedClaims) Valid() error { return nil } + +var _ IDTokenVerifier = new(ProviderMicrosoft) + +func (p *ProviderMicrosoft) Verify(ctx context.Context, rawIDToken string) (*Claims, error) { + keySet := gooidc.NewRemoteKeySet(ctx, p.JWKSUrl) + ctx = gooidc.ClientContext(ctx, p.reg.HTTPClient(ctx).HTTPClient) + return verifyToken(ctx, keySet, p.config, rawIDToken, p.config.IssuerURL) +}