Skip to content

Commit

Permalink
feat: support native social sign using apple/google sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
jonas-jonas committed Sep 4, 2023
1 parent 085d500 commit 6ec19d3
Show file tree
Hide file tree
Showing 18 changed files with 338 additions and 23 deletions.
24 changes: 24 additions & 0 deletions contrib/quickstart/kratos/email-password/kratos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ selfservice:
default_browser_return_url: http://127.0.0.1:4455/
allowed_return_urls:
- http://127.0.0.1:4455
- exp://localhost:8081

methods:
password:
Expand All @@ -28,6 +29,26 @@ selfservice:
enabled: true
code:
enabled: true
oidc:
enabled: true
config:
providers:
- apple_private_key: |
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgnnH+8HaqoX0wRH/d
O84FojXYtxvCvWY8n7LyuPSY4n6gCgYIKoZIzj0DAQehRANCAAS6AlP9bq3CZjN3
1+4K2J/kWFSyidygZD+ai0Pr83t7A7YvpNhoW5te2/b3kQLII9yp1dqwMH/UICvj
tjBazf0E
-----END PRIVATE KEY-----
apple_private_key_id: C723NT4579
apple_team_id: 44TJKWD29H
client_id: host.exp.Exponent
id: apple
label: apple
mapper_url: base64://bG9jYWwgY2xhaW1zID0gewogIGVtYWlsX3ZlcmlmaWVkOiBmYWxzZSwKfSArIHN0ZC5leHRWYXIoJ2NsYWltcycpOwoKewogIGlkZW50aXR5OiB7CiAgICB0cmFpdHM6IHsKICAgICAgLy8gQWxsb3dpbmcgdW52ZXJpZmllZCBlbWFpbCBhZGRyZXNzZXMgZW5hYmxlcyBhY2NvdW50CiAgICAgIC8vIGVudW1lcmF0aW9uIGF0dGFja3MsICBpZiB0aGUgdmFsdWUgaXMgdXNlZCBmb3IKICAgICAgLy8gdmVyaWZpY2F0aW9uIG9yIGFzIGEgcGFzc3dvcmQgbG9naW4gaWRlbnRpZmllci4KICAgICAgLy8KICAgICAgLy8gVGhlcmVmb3JlIHdlIG9ubHkgcmV0dXJuIHRoZSBlbWFpbCBpZiBpdCAoYSkgZXhpc3RzIGFuZCAoYikgaXMgbWFya2VkIHZlcmlmaWVkCiAgICAgIC8vIGJ5IEFwcGxlLgogICAgICBbaWYgJ2VtYWlsJyBpbiBjbGFpbXMgJiYgY2xhaW1zLmVtYWlsX3ZlcmlmaWVkIHRoZW4gJ2VtYWlsJyBlbHNlIG51bGxdOiBjbGFpbXMuZW1haWwsCiAgICB9LAogICAgdmVyaWZpZWRfYWRkcmVzc2VzOiBbCiAgICAgIGlmICdlbWFpbCcgaW4gY2xhaW1zICYmIGNsYWltcy5lbWFpbF92ZXJpZmllZCB0aGVuIHsgdmlhOiAiZW1haWwiLCB2YWx1ZTogY2xhaW1zLmVtYWlsIH0sCiAgICBdLAogIH0sCn0=
provider: apple
scope:
- email
flows:
error:
Expand Down Expand Up @@ -66,6 +87,9 @@ selfservice:
hooks:
- hook: session
- hook: show_verification_ui
oidc:
hooks:
- hook: session

log:
level: debug
Expand Down
37 changes: 37 additions & 0 deletions internal/client-go/model_update_login_flow_with_oidc_method.go

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

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

37 changes: 37 additions & 0 deletions internal/httpclient/model_update_login_flow_with_oidc_method.go

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

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

3 changes: 3 additions & 0 deletions selfservice/flow/login/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ type Flow struct {
//
// required: true
State State `json:"state" faker:"-" db:"state"`

// Only used internally
IDToken string `json:"-" db:"-"`
}

var _ flow.Flow = new(Flow)
Expand Down
5 changes: 4 additions & 1 deletion selfservice/flow/login/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ func (e *HookExecutor) PostLoginHook(
Method: a.Active.String(),
SSOProvider: provider,
}))
if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, g); err != nil {
if a.IDToken != "" {
// We don't want to redirect with the code, if the flow was submitted with an ID token.
// This is the case for Sign in with native Apple SDK or Google SDK.
} else if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, g); err != nil {
return errors.WithStack(err)
} else if handled {
return nil
Expand Down
3 changes: 3 additions & 0 deletions selfservice/flow/registration/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type Flow struct {
// and only on creating the flow.
SessionTokenExchangeCode string `json:"session_token_exchange_code,omitempty" faker:"-" db:"-"`

// only used internally
IDToken string `json:"-" faker:"-" db:"-"`

// State represents the state of this request:
//
// - choose_method: ask the user to choose a method (e.g. registration with email)
Expand Down
5 changes: 4 additions & 1 deletion selfservice/flow/registration/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
Debug("Post registration execution hooks completed successfully.")

if a.Type == flow.TypeAPI || x.IsJSONRequest(r) {
if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, ct.ToUiNodeGroup()); err != nil {
if a.IDToken != "" {
// We don't want to redirect with the code, if the flow was submitted with an ID token.
// This is the case for Sign in with native Apple SDK or Google SDK.
} else if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, ct.ToUiNodeGroup()); err != nil {
return errors.WithStack(err)
} else if handled {
return nil
Expand Down
4 changes: 3 additions & 1 deletion selfservice/hook/session_issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ func (e *SessionIssuer) ExecutePostRegistrationPostPersistHook(w http.ResponseWr

func (e *SessionIssuer) executePostRegistrationPostPersistHook(w http.ResponseWriter, r *http.Request, a *registration.Flow, s *session.Session) error {
if a.Type == flow.TypeAPI {
if s.AuthenticatedVia(identity.CredentialsTypeOIDC) {
// We don't want to redirect with the code, if the flow was submitted with an ID token.
// This is the case for Sign in with native Apple SDK or Google SDK.
if s.AuthenticatedVia(identity.CredentialsTypeOIDC) && a.IDToken == "" {
if handled, err := e.r.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, node.OpenIDConnectGroup); err != nil {
return errors.WithStack(err)
} else if handled {
Expand Down
4 changes: 4 additions & 0 deletions selfservice/strategy/oidc/.schema/link.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
},
"additionalProperties": false
}
},
"id_token": {
"type": "string",
"description": "An optional id token provided by an OIDC provider"
}
}
}
4 changes: 4 additions & 0 deletions selfservice/strategy/oidc/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type TokenExchanger interface {
Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
}

type IDTokenVerifier interface {
Verify(ctx context.Context, rawIDToken string) (*Claims, error)
}

// ConvertibleBoolean is used as Apple casually sends the email_verified field as a string.
type Claims struct {
Issuer string `json:"iss,omitempty"`
Expand Down
19 changes: 19 additions & 0 deletions selfservice/strategy/oidc/provider_apple.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"net/url"
"time"

"github.com/coreos/go-oidc"
"github.com/golang-jwt/jwt/v4"

"github.com/pkg/errors"
Expand Down Expand Up @@ -146,3 +147,21 @@ func decodeQuery(query url.Values, claims *Claims) {
}
}
}

var _ IDTokenVerifier = new(ProviderApple)

func (a *ProviderApple) Verify(ctx context.Context, rawIDToken string) (*Claims, error) {
keySet := oidc.NewRemoteKeySet(ctx, "https://appleid.apple.com/auth/keys")
verifier := oidc.NewVerifier("https://appleid.apple.com", keySet, &oidc.Config{
ClientID: a.config.ClientID,
})
token, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
return nil, err
}
claims := &Claims{}
if err := token.Claims(claims); err != nil {
return nil, err
}
return claims, nil
}
Loading

0 comments on commit 6ec19d3

Please sign in to comment.