Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Social login with Steam / Open ID 2.0 #3762

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const (
ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors"
ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer"
ViperKeyOIDCBaseRedirectURL = "selfservice.methods.oidc.config.base_redirect_uri"
ViperKeyOid2BaseRedirectURL = "selfservice.methods.oid2.config.base_redirect_uri"
ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name"
ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id"
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
Expand Down Expand Up @@ -590,6 +591,10 @@ func (p *Config) OIDCRedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeyOIDCBaseRedirectURL, p.SelfPublicURL(ctx))
}

func (p *Config) Oid2RedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeyOid2BaseRedirectURL, p.SelfPublicURL(ctx))
}

func (p *Config) IdentityTraitsSchemas(ctx context.Context) (ss Schemas, err error) {
if err = p.GetProvider(ctx).Koanf.Unmarshal(ViperKeyIdentitySchemas, &ss); err != nil {
return ss, nil
Expand Down
2 changes: 2 additions & 0 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package driver
import (
"context"
"crypto/sha256"
"github.com/ory/kratos/selfservice/strategy/oid2"
"net/http"
"strings"
"sync"
Expand Down Expand Up @@ -313,6 +314,7 @@ func (m *RegistryDefault) selfServiceStrategies() []any {
// Construct the default list of strategies
m.selfserviceStrategies = []any{
password.NewStrategy(m),
oid2.NewStrategy(m),
oidc.NewStrategy(m),
profile.NewStrategy(m),
code.NewStrategy(m),
Expand Down
75 changes: 75 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,45 @@
}
]
},
"selfServiceOid2Provider": {
"type": "object",
"properties": {
"id": {
"type": "string",
"examples": [
"steam"
]
},
"provider": {
"title": "Provider",
"description": "Can be one of generic, steam.",
"type": "string",
"enum": [
"generic",
"steam"
],
"examples": [
"steam"
]
},
"label": {
"title": "Optional string which will be used when generating labels for UI buttons.",
"type": "string"
},
"discovery_url": {
"type": "string",
"format": "uri",
"examples": [
"https://accounts.google.com/o/oauth2/v2/auth"
]
}
},
"additionalProperties": false,
"required": [
"id",
"provider"
]
},
"selfServiceHooks": {
"type": "array",
"items": {
Expand Down Expand Up @@ -1722,6 +1761,42 @@
}
}
}
},
"oid2": {
"type": "object",
"title": "Specify OpenID 2.0 Configuration",
"showEnvVarBlockForObject": true,
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"title": "Enables OpenID 2.0 Method",
"default": false
},
"config": {
"type": "object",
"additionalProperties": false,
"properties": {
"base_redirect_uri": {
"type": "string",
"title": "Base URL for OpenID 2.0 Redirect URIs",
"description": "Can be used to modify the base URL for OpenID 2.0 Redirect URLs. If unset, the Public Base URL will be used.",
"format": "uri",
"examples": [
"https://auth.myexample.org/"
]
},
"providers": {
"title": "OpenID 2.0 Providers",
"description": "A list and configuration of OpenID 2.0 providers Ory Kratos should integrate with.",
"type": "array",
"items": {
"$ref": "#/definitions/selfServiceOid2Provider"
}
}
}
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ require (
github.com/tidwall/gjson v1.14.3
github.com/tidwall/sjson v1.2.5
github.com/urfave/negroni v1.0.0
github.com/yohcop/openid-go v1.0.1
github.com/zmb3/spotify/v2 v2.4.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0
go.opentelemetry.io/otel v1.22.0
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -1111,6 +1113,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -1207,6 +1211,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -1323,6 +1329,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -1337,6 +1345,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
5 changes: 5 additions & 0 deletions identity/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type CredentialsType string
// Please make sure to add all of these values to the test that ensures they are created during migration
const (
CredentialsTypePassword CredentialsType = "password"
CredentialsTypeOID2 CredentialsType = "oid2"
CredentialsTypeOIDC CredentialsType = "oidc"
CredentialsTypeTOTP CredentialsType = "totp"
CredentialsTypeLookup CredentialsType = "lookup_secret"
Expand All @@ -96,6 +97,8 @@ func (c CredentialsType) ToUiNodeGroup() node.UiNodeGroup {
switch c {
case CredentialsTypePassword:
return node.PasswordGroup
case CredentialsTypeOID2:
return node.OpenID2Group
case CredentialsTypeOIDC:
return node.OpenIDConnectGroup
case CredentialsTypeTOTP:
Expand All @@ -113,6 +116,7 @@ func (c CredentialsType) ToUiNodeGroup() node.UiNodeGroup {

var AllCredentialTypes = []CredentialsType{
CredentialsTypePassword,
CredentialsTypeOID2,
CredentialsTypeOIDC,
CredentialsTypeTOTP,
CredentialsTypeLookup,
Expand All @@ -131,6 +135,7 @@ const (
func ParseCredentialsType(in string) (CredentialsType, bool) {
for _, t := range []CredentialsType{
CredentialsTypePassword,
CredentialsTypeOID2,
CredentialsTypeOIDC,
CredentialsTypeTOTP,
CredentialsTypeLookup,
Expand Down
62 changes: 62 additions & 0 deletions identity/credentials_oid2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package identity

import (
"bytes"
"encoding/json"
"fmt"

"github.com/pkg/errors"

"github.com/ory/kratos/x"
)

// CredentialsOid2 contains the configuration for credentials of the type oidc.
//
// swagger:model identityCredentialsOidc
type CredentialsOid2 struct {
Providers []CredentialsOid2Provider `json:"providers"`
}

// CredentialsOid2 Provider contains a specific OpenID 2.0 credential for a particular connection (e.g. Steam).
//
// swagger:model identityCredentialsOid2Provider
type CredentialsOid2Provider struct {
ClaimedId string `json:"claimed_id"`
Provider string `json:"provider"`
}

// NewCredentialsOid2 creates a new Open ID 2.0 credential.
func NewCredentialsOid2(claimedId, provider string) (*Credentials, error) {
if provider == "" {
return nil, errors.New("received empty provider in oid2 credentials")
}

if claimedId == "" {
return nil, errors.New("received empty claimed ID in oid2 credentials")
}

var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(CredentialsOid2{
Providers: []CredentialsOid2Provider{
{
ClaimedId: claimedId,
Provider: provider,
}},
}); err != nil {
return nil, errors.WithStack(x.PseudoPanic.
WithDebugf("Unable to encode password options to JSON: %s", err))
}

return &Credentials{
Type: CredentialsTypeOID2,
Identifiers: []string{Oid2UniqueID(provider, claimedId)},
Config: b.Bytes(),
}, nil
}

func Oid2UniqueID(provider, subject string) string {
return fmt.Sprintf("%s:%s", provider, subject)
}
2 changes: 1 addition & 1 deletion identity/credentials_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func NewCredentialsOIDC(tokens *CredentialsOIDCEncryptedTokens, provider, subjec
}

if subject == "" {
return nil, errors.New("received empty provider in oidc credentials")
return nil, errors.New("received empty subject in oidc credentials")
}

var b bytes.Buffer
Expand Down
20 changes: 20 additions & 0 deletions selfservice/strategy/oid2/.schema/link.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/password/login.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"csrf_token": {
"type": "string"
},
"provider": {
"type": "string",
"minLength": 1
},
"traits": {
"description": "DO NOT DELETE THIS FIELD. This field will be overwritten in login.go's and registration.go's decoder() method. Do not add anything to this field as it has no effect."
},
"method": {
"type": "string"
}
}
}
21 changes: 21 additions & 0 deletions selfservice/strategy/oid2/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package oid2

import (
"context"

"github.com/ory/x/urlx"

Check failure on line 9 in selfservice/strategy/oid2/provider.go

View workflow job for this annotation

GitHub Actions / Run tests and lints

File is not `goimports`-ed with -local github.com/ory (goimports)
"net/url"
"strings"
)

type Provider interface {
Config() *Configuration
GetRedirectUrl(ctx context.Context) string
}

func (providerConfig Configuration) Redir(public *url.URL) string {
return urlx.AppendPaths(public, strings.Replace(RouteCallback, ":provider", providerConfig.ID, 1)).String()
}
51 changes: 51 additions & 0 deletions selfservice/strategy/oid2/provider_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package oid2

import (
"github.com/ory/herodot"

Check failure on line 7 in selfservice/strategy/oid2/provider_config.go

View workflow job for this annotation

GitHub Actions / Run tests and lints

File is not `goimports`-ed with -local github.com/ory (goimports)
"github.com/pkg/errors"
"golang.org/x/exp/maps"
)

type Configuration struct {
// ID is the provider's ID
ID string `json:"id"`

// Provider is either "generic" for a generic OpenID 2.0 Provider or one of:
// - generic
// - steam
Provider string `json:"provider"`

// Label represents an optional label which can be used in the UI generation.
Label string `json:"label"`

// DiscoveryUrl is the URL of the Open ID 2.0 discovery document, typically something like:
// https://example.org/openid. Should only be used and when `provider` is set to `generic`.
DiscoveryUrl string `json:"discovery_url"`
}

type ConfigurationCollection struct {
BaseRedirectURI string `json:"base_redirect_uri"`
Providers []Configuration `json:"providers"`
}

var supportedProviders = map[string]func(config *Configuration, reg Dependencies) Provider{
"generic": NewProviderGenericOid2,
"steam": NewProviderSteam,
}

func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) {
for k := range c.Providers {
p := c.Providers[k]
if p.ID == id {
if f, ok := supportedProviders[p.Provider]; ok {
return f(&p, reg), nil
}

return nil, errors.Errorf("provider type %s is not supported, supported are: %v", p.Provider, maps.Keys(supportedProviders))
}
}
return nil, errors.WithStack(herodot.ErrNotFound.WithReasonf(`OpenID 2.0 Provider "%s" is unknown or has not been configured`, id))
}
Loading
Loading