├── .gitignore ├── assets ├── images │ ├── arrow.png │ ├── person.png │ ├── shopping-bag.png │ └── hellosocks-background.png └── css │ └── styles.css ├── public └── example-app-image.png ├── .env.template ├── .github └── dependabot.yml ├── CODEOWNERS ├── go.mod ├── templates ├── loggedOut.html ├── emailSent.html ├── loggedIn.html └── loginOrSignUp.html ├── LICENSE ├── README.md ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .env.local -------------------------------------------------------------------------------- /assets/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-go-magic-links/HEAD/assets/images/arrow.png -------------------------------------------------------------------------------- /assets/images/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-go-magic-links/HEAD/assets/images/person.png -------------------------------------------------------------------------------- /assets/images/shopping-bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-go-magic-links/HEAD/assets/images/shopping-bag.png -------------------------------------------------------------------------------- /public/example-app-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-go-magic-links/HEAD/public/example-app-image.png -------------------------------------------------------------------------------- /assets/images/hellosocks-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-go-magic-links/HEAD/assets/images/hellosocks-background.png -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # The below values may be found in your Stytch Dashboard: https://stytch.com/dashboard/api-keys 2 | STYTCH_PROJECT_ENV=test 3 | STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID" 4 | STYTCH_SECRET="YOUR_STYTCH_SECRET" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | allow: 6 | - dependency-name: github.com/stytchauth/stytch-go 7 | schedule: 8 | interval: daily -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Stytch code owners file 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo. Unless a later match takes precedence, 5 | # @stytchauth/developer-relations will be requested for 6 | # review when someone opens a pull request. 7 | * @stytchauth/developer-relations @stytchauth/engineering 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stytchauth/stytch-go-magic-links 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect 7 | github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 8 | github.com/gorilla/mux v1.8.1 9 | github.com/gorilla/sessions v1.2.2 10 | github.com/joho/godotenv v1.5.1 11 | github.com/kr/pretty v0.3.1 // indirect 12 | github.com/rogpeppe/go-internal v1.12.0 // indirect 13 | github.com/stytchauth/stytch-go/v12 v12.5.1 14 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /templates/loggedOut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello Socks | Stytch example 5 | 6 | 7 | 8 |
9 | 23 |
24 |
25 |
26 |

Enjoy your socks!

27 |

We'll see you next time.

28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 stytchauth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /templates/emailSent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello Socks | Stytch example 5 | 6 | 7 | 8 |
9 | 23 |
24 |
25 |
26 |

You've got mail!

27 |

Check your inbox (including the promotions folder)
for the login link.

28 |

Didn't find it? Send it again.

29 |
30 |
31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/loggedIn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello Socks | Stytch example 5 | 6 | 7 | 8 |
9 | 23 |
24 |
25 |
26 |

Welcome back {{.EmailAddress}}!

27 |

Your socks just can't wait to be shipped!

28 |
29 |
30 | Logout 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /templates/loginOrSignUp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello Socks | Stytch example 5 | 6 | 7 | 8 |
9 | 23 |
24 |
25 |
26 |

Almost there...

27 |

Sign up or log in to place your order.
No password required!

28 |
29 |
30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .app { 6 | display: flex; 7 | flex-direction: column; 8 | font-family: "Optima", sans-serif; 9 | height: 100vh; 10 | text-align: center; 11 | } 12 | 13 | .nav { 14 | align-items: center; 15 | background-color: black; 16 | color: white; 17 | display: flex; 18 | flex-direction: row; 19 | height: 55px; 20 | justify-content: space-between; 21 | padding-left: 20px; 22 | padding-right: 20px; 23 | } 24 | 25 | .navItem { 26 | margin-right: 30px; 27 | font-size: 20px; 28 | } 29 | 30 | .icon { 31 | height: 24px; 32 | margin-left: 5px; 33 | width: 24px; 34 | } 35 | 36 | .content { 37 | align-items: center; 38 | background-image: url("../images/hellosocks-background.png"); 39 | background-repeat: no-repeat; 40 | background-size: cover; 41 | display: flex; 42 | flex-grow: 1; 43 | justify-content: center; 44 | } 45 | 46 | .card { 47 | align-items: center; 48 | background-color: white; 49 | display: flex; 50 | height: 400px; 51 | justify-content: center; 52 | width: 720px; 53 | } 54 | 55 | .cardContent { 56 | text-align: center; 57 | } 58 | 59 | p { 60 | font-size: 20px; 61 | line-height: 26px; 62 | margin-bottom: 30px; 63 | } 64 | 65 | .actions { 66 | align-items: center; 67 | display: flex; 68 | flex-direction: row; 69 | justify-content: center; 70 | } 71 | 72 | input { 73 | border: 1px solid black; 74 | border-radius: 0px; 75 | font-family: "Optima", sans-serif; 76 | font-size: 16px; 77 | height: 18px; 78 | width: 280px; 79 | padding: 10px; 80 | } 81 | 82 | input:focus { 83 | outline: 0; 84 | } 85 | 86 | .arrow { 87 | border: none; 88 | cursor: pointer; 89 | height: 40px; 90 | margin-left: 5px; 91 | padding: 0; 92 | 93 | width: 40px; 94 | } 95 | 96 | a { 97 | font-size: 20px; 98 | } 99 | 100 | a:hover { 101 | cursor: pointer; 102 | text-decoration: underline; 103 | } 104 | 105 | h1 { 106 | font-size: 40px; 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stytch-go-magic-links 2 | 3 | This is a lightweight [Stytch](https://stytch.com) + [Go](https://go.dev/) example app which demonstrates a quick Stytch implementation using our [Email Magic Links](https://stytch.com/docs/guides/magic-links/email-magic-links/api) and [Sessions](https://stytch.com/docs/guides/sessions/using-sessions) products. 4 | 5 |

stytch

6 | 7 | # Running locally 8 | 9 | ## Set up 10 | 11 | ### In the Stytch Dashboard 12 | 13 | 1. Create a [Stytch](https://stytch.com/) account. Once your account is set up a Project called "My first project" will be automatically created for you. 14 | 15 | 2. Navigate to [Redirect URLs](https://stytch.com/dashboard/redirect-urls), and add `http://localhost:3000/authenticate` as the types **Login** and **Sign-up**. 16 | 17 | 3. Finally, navigate to [API Keys](https://stytch.com/dashboard/api-keys), and copy your `project_id` and `secret`. You will need these value later on. 18 | 19 | ### On your machine 20 | 21 | In your terminal, clone the project and install dependencies: 22 | 23 | ```bash 24 | git clone https://github.com/stytchauth/stytch-go-magic-links.git 25 | cd stytch-go-magic-links 26 | go get 27 | ``` 28 | 29 | Next, create a `.env.local` file by running the command below and replacing the keys with those copied from step 3 above. 30 | 31 | ```bash 32 | cp .env.template .env.local 33 | # Replace your keys in new .env.local file with the API keys from step 3 above 34 | ``` 35 | 36 | ## Running locally 37 | 38 | After completing all the set up steps above the application can be run with the command: 39 | 40 | ```go run main.go``` 41 | 42 | The application will be available at [`http://localhost:3000`](http://localhost:3000) and you'll be able to login with Email Magic Links! 43 | 44 | To do so, enter your email, then check for the Stytch email and click the sign in button. 45 | 46 | You should be signed in! 47 | 48 | ## Next steps 49 | 50 | This example app showcases a small portion of what you can accomplish with Stytch. Here are a few ideas to explore: 51 | 52 | 1. Add additional login methods like [Passwords](https://stytch.com/docs/guides/passwords/api) or [Passcodes](https://stytch.com/docs/guides/passcodes/api). 53 | 2. Secure your app further by building MFA authentication using methods like [WebAuthn](https://stytch.com/docs/guides/webauthn/api). 54 | 55 | 56 | ## Get help and join the community 57 | 58 | #### :speech_balloon: Stytch community Slack 59 | 60 | Join the discussion, ask questions, and suggest new features in our ​[Slack community](https://stytch.com/docs/resources/support/overview)! 61 | 62 | #### :question: Need support? 63 | 64 | Check out the [Stytch Forum](https://forum.stytch.com/) or email us at [support@stytch.com](mailto:support@stytch.com). 65 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/MicahParks/keyfunc/v2 v2.0.1/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= 2 | github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= 3 | github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 9 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 10 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 11 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 12 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 13 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 14 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 15 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 16 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 17 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 18 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 19 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 20 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 21 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 22 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 23 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 26 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 27 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 28 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 29 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 30 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 34 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 35 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 38 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 39 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 40 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 41 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 42 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 43 | github.com/stytchauth/stytch-go/v12 v12.5.1 h1:l5TAZAB2+1p72ChCOgl45cxc4XIpIB4r4hNOe87l0LY= 44 | github.com/stytchauth/stytch-go/v12 v12.5.1/go.mod h1:nKdEsZ+u8LOB18Q82i3jDNhL7yMQxnmLdagI6SSzsrs= 45 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 46 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 47 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 48 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 49 | golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 50 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 51 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 52 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 53 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 55 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 56 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 57 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 60 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 61 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 62 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 63 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 64 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 67 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 68 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 71 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 72 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "html/template" 8 | "log" 9 | "net/http" 10 | "os" 11 | 12 | "github.com/gorilla/mux" 13 | gorillaSessions "github.com/gorilla/sessions" 14 | "github.com/joho/godotenv" 15 | "github.com/stytchauth/stytch-go/v12/stytch/consumer/magiclinks" 16 | "github.com/stytchauth/stytch-go/v12/stytch/consumer/magiclinks/email" 17 | "github.com/stytchauth/stytch-go/v12/stytch/consumer/sessions" 18 | "github.com/stytchauth/stytch-go/v12/stytch/consumer/stytchapi" 19 | "github.com/stytchauth/stytch-go/v12/stytch/consumer/users" 20 | ) 21 | 22 | var ( 23 | store = gorillaSessions.NewCookieStore([]byte("your-secret-key")) 24 | ) 25 | 26 | type config struct { 27 | address string 28 | fullAddress string 29 | stytchClient *stytchapi.API 30 | } 31 | 32 | // struct to hold the values to be passed to the html templates 33 | type templateVariables struct { 34 | LoginOrCreateUserPath string 35 | LoggedOutPath string 36 | EmailAddress string 37 | } 38 | 39 | func main() { 40 | // Load .env & set config 41 | c, err := initializeConfig() 42 | if err != nil { 43 | log.Fatal("error initializing config") 44 | } 45 | 46 | r := mux.NewRouter() 47 | fmt.Println("Navigate to", c.fullAddress, "to see the Hello Socks app!") 48 | 49 | // routes 50 | r.HandleFunc("/", c.homepage).Methods("GET") 51 | r.HandleFunc("/login_or_create_user", c.loginOrCreateUser).Methods("POST") 52 | r.HandleFunc("/authenticate", c.authenticate).Methods("GET") 53 | r.HandleFunc("/logout", c.logout).Methods("GET") 54 | 55 | // Declare the static file directory 56 | // this is to ensure our static assets & css are accessible & rendered 57 | staticFileDirectory := http.Dir("./assets/") 58 | staticFileHandler := http.StripPrefix("/assets/", http.FileServer(staticFileDirectory)) 59 | r.PathPrefix("/assets/").Handler(staticFileHandler) 60 | 61 | log.Fatal(http.ListenAndServe(c.address, r)) 62 | } 63 | 64 | // handles the homepage for Hello Socks 65 | func (c *config) homepage(w http.ResponseWriter, r *http.Request) { 66 | user := c.getAuthenticatedUser(w, r) 67 | 68 | if user != nil { 69 | parseAndExecuteTemplate( 70 | "templates/loggedIn.html", 71 | &templateVariables{LoggedOutPath: c.fullAddress + "/logout", EmailAddress: user.Emails[0].Email}, 72 | w, 73 | ) 74 | } 75 | 76 | parseAndExecuteTemplate( 77 | "templates/loginOrSignUp.html", 78 | &templateVariables{LoginOrCreateUserPath: c.fullAddress + "/login_or_create_user"}, 79 | w, 80 | ) 81 | 82 | } 83 | 84 | // takes the email entered on the homepage and hits the stytch 85 | // loginOrCreateUser endpoint to send the user a magic link 86 | func (c *config) loginOrCreateUser(w http.ResponseWriter, r *http.Request) { 87 | _, err := c.stytchClient.MagicLinks.Email.LoginOrCreate( 88 | context.Background(), 89 | &email.LoginOrCreateParams{ 90 | Email: r.FormValue("email"), 91 | }) 92 | if err != nil { 93 | log.Printf("something went wrong sending magic link: %s\n", err) 94 | } 95 | 96 | parseAndExecuteTemplate("templates/emailSent.html", nil, w) 97 | } 98 | 99 | // this is the endpoint the link in the magic link hits takes the token from the 100 | // link's query params and hits the stytch authenticate endpoint to verify the token is valid 101 | func (c *config) authenticate(w http.ResponseWriter, r *http.Request) { 102 | resp, err := c.stytchClient.MagicLinks.Authenticate( 103 | context.Background(), 104 | &magiclinks.AuthenticateParams{ 105 | Token: r.URL.Query().Get("token"), 106 | SessionDurationMinutes: 60, 107 | }) 108 | if err != nil { 109 | log.Printf("something went wrong authenticating the magic link: %s\n", err) 110 | } 111 | 112 | session, err := store.Get(r, "stytch_session") 113 | if err != nil { 114 | http.Error(w, err.Error(), http.StatusInternalServerError) 115 | return 116 | } 117 | 118 | session.Values["token"] = resp.SessionToken 119 | session.Save(r, w) 120 | 121 | c.homepage(w, r) 122 | } 123 | 124 | // handles the logout endpoint 125 | func (c *config) logout(w http.ResponseWriter, r *http.Request) { 126 | session, err := store.Get(r, "stytch_session") 127 | if err != nil { 128 | log.Printf("error getting gorilla session: %s\n", err) 129 | } 130 | session.Options.MaxAge = -1 131 | session.Save(r, w) 132 | 133 | parseAndExecuteTemplate("templates/loggedOut.html", nil, w) 134 | } 135 | 136 | // handles returning the authenticated user, if valid session present 137 | func (c *config) getAuthenticatedUser(w http.ResponseWriter, r *http.Request) *users.User { 138 | session, err := store.Get(r, "stytch_session") 139 | if err != nil || session == nil { 140 | return nil 141 | } 142 | 143 | token, ok := session.Values["token"].(string) 144 | if !ok || token == "" { 145 | return nil 146 | } 147 | 148 | resp, err := c.stytchClient.Sessions.Authenticate( 149 | context.Background(), 150 | &sessions.AuthenticateParams{ 151 | SessionToken: token, 152 | }) 153 | if err != nil { 154 | delete(session.Values, "token") 155 | session.Save(r, w) 156 | return nil 157 | } 158 | session.Values["token"] = resp.SessionToken 159 | session.Save(r, w) 160 | 161 | return &resp.User 162 | } 163 | 164 | // helper function to parse the template & render it with any provided data 165 | func parseAndExecuteTemplate(temp string, templateVars *templateVariables, w http.ResponseWriter) { 166 | t, err := template.ParseFiles(temp) 167 | if err != nil { 168 | log.Printf("something went wrong parsing template: %s\n", err) 169 | } 170 | 171 | err = t.Execute(w, templateVars) 172 | if err != nil { 173 | log.Printf("something went wrong executing the template: %s\n", err) 174 | } 175 | } 176 | 177 | // helper function so see if a key is in the .env file 178 | // if so return that value, otherwise return the default value 179 | func getEnv(key string, defaultValue string) string { 180 | value, exists := os.LookupEnv(key) 181 | if value, exists = os.LookupEnv(key); exists { 182 | return value 183 | } 184 | return defaultValue 185 | } 186 | 187 | // helper function to load in the .env file & set config values 188 | func initializeConfig() (*config, error) { 189 | if err := godotenv.Load(".env.local"); err != nil { 190 | log.Printf("No .env file found at '%s'", ".env.local") 191 | return &config{}, errors.New("error loading .env.local file") 192 | } 193 | address := getEnv("ADDRESS", "localhost:3000") 194 | 195 | // define the stytch client using your stytch project id & secret 196 | // use stytch.EnvLive if you want to hit the live api 197 | stytchAPIClient, err := stytchapi.NewClient( 198 | os.Getenv("STYTCH_PROJECT_ID"), 199 | os.Getenv("STYTCH_SECRET"), 200 | ) 201 | if err != nil { 202 | log.Fatalf("error instantiating API client %s", err) 203 | } 204 | 205 | return &config{ 206 | address: address, 207 | fullAddress: "http://" + address, 208 | stytchClient: stytchAPIClient, 209 | }, nil 210 | 211 | } 212 | --------------------------------------------------------------------------------