Skip to content

Commit

Permalink
feat(outh2): add facebook sign in
Browse files Browse the repository at this point in the history
  • Loading branch information
devcodes9 committed Oct 2, 2024
1 parent a7e4c67 commit 5ff1d6f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 2 deletions.
5 changes: 4 additions & 1 deletion backend/config/config_third_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package config
import (
"errors"
"fmt"
"strings"

"github.com/fatih/structs"
"github.com/gobwas/glob"
"github.com/invopop/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
"strings"
)

type ThirdParty struct {
Expand Down Expand Up @@ -113,6 +114,8 @@ type ThirdPartyProviders struct {
LinkedIn ThirdPartyProvider `yaml:"linkedin" json:"linkedin,omitempty" koanf:"linkedin"`
// `microsoft` contains the provider configuration for Microsoft.
Microsoft ThirdPartyProvider `yaml:"microsoft" json:"microsoft,omitempty" koanf:"microsoft"`
// `facebook` contains the provider configuration for Facebook.
Facebook ThirdPartyProvider `yaml:"facebook" json:"facebook,omitempty" koanf:"facebook"`
}

func (p *ThirdPartyProviders) Validate() error {
Expand Down
4 changes: 3 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/teamhanko/hanko/backend

go 1.20
go 1.21

toolchain go1.23.2

require (
github.com/brianvoe/gofakeit/v6 v6.28.0
Expand Down
9 changes: 9 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ClickHouse/ch-go v0.55.0 h1:jw4Tpx887YXrkyL5DfgUome/po8MLz92nz2heOQ6RjQ=
github.com/ClickHouse/ch-go v0.55.0/go.mod h1:kQT2f+yp2p+sagQA/7kS6G3ukym+GQ5KAu1kuFAFDiU=
github.com/ClickHouse/clickhouse-go/v2 v2.9.1 h1:IeE2bwVvAba7Yw5ZKu98bKI4NpDmykEy6jUaQdJJCk8=
Expand Down Expand Up @@ -78,12 +79,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
Expand Down Expand Up @@ -126,6 +129,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
Expand Down Expand Up @@ -188,7 +192,9 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -222,6 +228,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -372,6 +379,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
Expand Down Expand Up @@ -883,6 +891,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
Expand Down
2 changes: 2 additions & 0 deletions backend/thirdparty/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ func GetProvider(config config.ThirdParty, name string) (OAuthProvider, error) {
return NewMicrosoftProvider(config.Providers.Microsoft, config.RedirectURL)
case "linkedin":
return NewLinkedInProvider(config.Providers.LinkedIn, config.RedirectURL)
case "facebook":
return NewFacebookProvider(config.Providers.Facebook, config.RedirectURL)
default:
return nil, fmt.Errorf("provider '%s' is not supported", name)
}
Expand Down
154 changes: 154 additions & 0 deletions backend/thirdparty/provider_facebook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package thirdparty

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/teamhanko/hanko/backend/config"
"golang.org/x/oauth2"
)

const (
FacebookAPIBase = "https://graph.facebook.com"
FacebookOauthAuthEndpoint = "https://www.facebook.com/v10.0/dialog/oauth"
FacebookOauthTokenEndpoint = FacebookAPIBase + "/v10.0/oauth/access_token"
FacebookUserInfoEndpoint = FacebookAPIBase + "/me?fields=id,name,email,picture"
FacebookDebugTokenEndpoint = FacebookAPIBase + "/debug_token"
)

var DefaultFacebookScopes = []string{
"email",
"public_profile",
}

type facebookProvider struct {
*oauth2.Config
}

type FacebookUser struct {
ID string `json:"id"`
Name string `json:"name"`
Picture struct {
Data struct {
URL string `json:"url"`
} `json:"data"`
} `json:"picture"`
Email string `json:"email"`
Verified bool `json:"verified"`
}

// TokenDebugResponse represents the response from the Facebook debug token API.
type TokenDebugResponse struct {
Data struct {
IsValid bool `json:"is_valid"`
UserID string `json:"user_id"`
AppID string `json:"app_id"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
} `json:"data"`
}

// NewFacebookProvider creates a Facebook third party provider.
func NewFacebookProvider(config config.ThirdPartyProvider, redirectURL string) (OAuthProvider, error) {
if !config.Enabled {
return nil, errors.New("facebook provider is disabled")
}

return &facebookProvider{
Config: &oauth2.Config{
ClientID: config.ClientID,
ClientSecret: config.Secret,
Endpoint: oauth2.Endpoint{
AuthURL: FacebookOauthAuthEndpoint,
TokenURL: FacebookOauthTokenEndpoint,
},
Scopes: DefaultFacebookScopes,
RedirectURL: redirectURL,
},
}, nil
}

func (g facebookProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
return g.Exchange(context.Background(), code)
}

func (g facebookProvider) GetUserData(token *oauth2.Token) (*UserData, error) {
client := g.Client(context.Background(), token)

// Get user data from Facebook API.
resp, err := client.Get(FacebookUserInfoEndpoint)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var user FacebookUser
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, err
}

// Verify email by calling the token debug endpoint
emailVerified, err := g.verifyEmail(token)
if err != nil {
return nil, err
}

user.Verified = emailVerified

data := &UserData{}
if user.Email != "" {
data.Emails = append(data.Emails, Email{
Email: user.Email,
Verified: user.Verified,
Primary: true,
})
}

if len(data.Emails) <= 0 {
return nil, errors.New("unable to find email with Facebook provider")
}

data.Metadata = &Claims{
Issuer: FacebookAPIBase,
Subject: user.ID,
Name: user.Name,
Picture: user.Picture.Data.URL,
Email: user.Email,
EmailVerified: user.Verified,
}

return data, nil
}

// verifyEmail checks if the user's email is verified using the Facebook debug token endpoint.
func (g facebookProvider) verifyEmail(token *oauth2.Token) (bool, error) {
// Build the URL for the token debug endpoint
appAccessToken := fmt.Sprintf("%s|%s", g.ClientID, g.ClientSecret)
debugTokenURL := fmt.Sprintf("%s?input_token=%s&access_token=%s", FacebookDebugTokenEndpoint, token.AccessToken, appAccessToken)

// Make the HTTP request to the Facebook token debug endpoint
resp, err := http.Get(debugTokenURL)
if err != nil {
return false, err
}
defer resp.Body.Close()

var debugResponse TokenDebugResponse
if err := json.NewDecoder(resp.Body).Decode(&debugResponse); err != nil {
return false, err
}

// Check if the token is valid and if the email is verified
if !debugResponse.Data.IsValid {
return false, errors.New("invalid Facebook OAuth token")
}

return debugResponse.Data.EmailVerified, nil
}

func (g facebookProvider) Name() string {
return "facebook"
}

0 comments on commit 5ff1d6f

Please sign in to comment.