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 6b580e5
Show file tree
Hide file tree
Showing 17 changed files with 314 additions and 23 deletions.
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
}
19 changes: 18 additions & 1 deletion selfservice/strategy/oidc/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func (s *Strategy) handleCallback(w http.ResponseWriter, r *http.Request, ps htt
return
case *registration.Flow:
a.TransientPayload = cntnr.TransientPayload
if ff, err := s.processRegistration(w, r, a, token, claims, provider, cntnr); err != nil {
if ff, err := s.processRegistration(w, r, a, token, claims, provider, cntnr, ""); err != nil {
if ff != nil {
s.forwardError(w, r, ff, err)
return
Expand Down Expand Up @@ -589,3 +589,20 @@ func (s *Strategy) CompletedAuthenticationMethod(ctx context.Context) session.Au
AAL: identity.AuthenticatorAssuranceLevel1,
}
}

func (s *Strategy) processIDToken(w http.ResponseWriter, r *http.Request, provider Provider, idToken string) (*Claims, error) {
verifier, ok := provider.(IDTokenVerifier)
if !ok {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Provider does not support ID Token verification.")) // TODO: move to global var
}
claims, err := verifier.Verify(r.Context(), idToken)
if err != nil {
return nil, errors.WithStack(err)
}

if err := claims.Validate(); err != nil {
return nil, errors.WithStack(err)
}

return claims, nil
}
Loading

0 comments on commit 6b580e5

Please sign in to comment.