You've got mail!
27 |Check your inbox (including the promotions folder)
for the login link.
Didn't find it? Send it again.
29 |├── .env ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── main.py ├── main_async.py ├── requirements.txt ├── static ├── css │ └── styles.css └── images │ ├── arrow.png │ ├── hellosocks-background.png │ ├── person.png │ └── shopping-bag.png └── templates ├── emailSent.html ├── loggedIn.html ├── loggedOut.html └── loginOrSignUp.html /.env: -------------------------------------------------------------------------------- 1 | HOST='localhost' 2 | PORT='3000' 3 | STYTCH_PROJECT_ID='' 4 | STYTCH_SECRET='' 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .vscode/ 3 | .venv 4 | venv 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stytch-python-magic-links 2 | 3 | ##### Prerequisites 4 | 5 | Ensure you have pip, python and virtualenv installed 6 | 7 | ##### 1. Clone the repository. 8 | 9 | Close this repository and navigate to the folder with the source code on your machine in a terminal window. 10 | 11 | ##### 2. Setup a virtualenv 12 | 13 | We suggest creating a [virtualenv](https://docs.python.org/3/library/venv.html) and activating it to avoid installing dependencies globally 14 | 15 | - `python3 -m venv venv` 16 | - `source venv/bin/activate` 17 | 18 | ##### 3. Install dependencies: 19 | 20 | `pip install -r requirements.txt` 21 | 22 | ##### 4. Set ENV vars 23 | 24 | Set your project ID and secret in the `.env` file. 25 | 26 | ##### 5. Add Magic Link URL 27 | 28 | Visit https://stytch.com/dashboard/redirect-urls to add 29 | `http://localhost:3000/authenticate` as a valid sign-up and login URL. 30 | 31 | ##### 6. Run the Server 32 | 33 | Run `python3 main.py` 34 | If you're interested in running async instead, run `python3 main_async.py` 35 | 36 | ##### 7. Login 37 | 38 | Visit `http://localhost:3000` and login with your email. 39 | Then check for the Stytch email and click the sign in button. 40 | You should be signed in! 41 | 42 | ## Next steps 43 | 44 | This example app showcases a small portion of what you can accomplish with Stytch. Here are a few ideas to explore: 45 | 46 | 1. Add additional login methods like [OAuth](https://stytch.com/docs/api/oauth-google-start) or [Passwords](https://stytch.com/docs/api/password-create). 47 | 2. Secure your app further by building MFA authentication using methods like [OTP](https://stytch.com/docs/api/send-otp-by-sms). 48 | 49 | ## Get help and join the community 50 | 51 | #### :speech_balloon: Stytch community Slack 52 | 53 | Join the discussion, ask questions, and suggest new features in our [Slack community](https://stytch.com/docs/resources/support/overview)! 54 | 55 | #### :question: Need support? 56 | 57 | Check out the [Stytch Forum](https://forum.stytch.com/) or email us at [support@stytch.com](mailto:support@stytch.com). 58 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | import dotenv 7 | import stytch 8 | from flask import Flask, render_template, request, session, redirect, url_for 9 | 10 | # load the .env file 11 | dotenv.load_dotenv() 12 | 13 | # By default, run on localhost:3000 14 | HOST = os.getenv("HOST", "localhost") 15 | PORT = int(os.getenv("PORT", "3000")) 16 | 17 | # Load the Stytch credentials, but quit if they aren't defined 18 | STYTCH_PROJECT_ID = os.getenv("STYTCH_PROJECT_ID") 19 | STYTCH_SECRET = os.getenv("STYTCH_SECRET") 20 | if STYTCH_PROJECT_ID is None: 21 | sys.exit("STYTCH_PROJECT_ID env variable must be set before running") 22 | if STYTCH_SECRET is None: 23 | sys.exit("STYTCH_SECRET env variable must be set before running") 24 | 25 | # NOTE: Set environment to "live" if you want to hit the live api 26 | stytch_client = stytch.Client( 27 | project_id=STYTCH_PROJECT_ID, 28 | secret=STYTCH_SECRET, 29 | environment="test", 30 | ) 31 | 32 | # create a Flask web app 33 | app = Flask(__name__) 34 | app.secret_key = 'some-secret-key' 35 | 36 | 37 | # handles the homepage for Hello Socks 38 | # if active session, shows user profile 39 | # otherwise prompts user to login 40 | @app.route("/") 41 | def index() -> str: 42 | user = get_authenticated_user() 43 | if user is None: 44 | return render_template("loginOrSignUp.html") 45 | 46 | return render_template("loggedIn.html", email=user.emails[0].email) 47 | 48 | # takes the email entered on the homepage and hits the stytch 49 | # loginOrCreateUser endpoint to send the user a magic link 50 | @app.route("/login_or_create_user", methods=["POST"]) 51 | def login_or_create_user() -> str: 52 | resp = stytch_client.magic_links.email.login_or_create( 53 | email=request.form["email"] 54 | ) 55 | 56 | if resp.status_code != 200: 57 | print(resp) 58 | return "something went wrong sending magic link" 59 | return render_template("emailSent.html") 60 | 61 | 62 | # This is the endpoint the link in the magic link hits. 63 | # It takes the token from the link's query params and hits the 64 | # stytch authenticate endpoint to verify the token is valid 65 | @app.route("/authenticate") 66 | def authenticate() -> str: 67 | resp = stytch_client.magic_links.authenticate(request.args["token"], session_duration_minutes=60) 68 | if resp.status_code != 200: 69 | print(resp) 70 | return "something went wrong authenticating token" 71 | session["stytch_session_token"] = resp.session_token 72 | return redirect(url_for("index")) 73 | 74 | # handles the logout endpoint 75 | @app.route("/logout") 76 | def logout() -> str: 77 | session.pop("stytch_session_token", None) 78 | return render_template("loggedOut.html") 79 | 80 | # Helper method for session authentication 81 | def get_authenticated_user(): 82 | stytch_session = session.get("stytch_session_token") 83 | if not stytch_session: 84 | return None 85 | resp = stytch_client.sessions.authenticate(session_token=stytch_session) 86 | if resp.status_code != 200: 87 | return None 88 | return resp.user 89 | 90 | # run's the app on the provided host & port 91 | if __name__ == "__main__": 92 | # in production you would want to make sure to disable debugging 93 | app.run(host=HOST, port=PORT, debug=True) 94 | -------------------------------------------------------------------------------- /main_async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | import dotenv 7 | import stytch 8 | from flask import Flask, render_template, request, session, redirect, url_for 9 | 10 | # load the .env file 11 | dotenv.load_dotenv() 12 | 13 | # By default, run on localhost:3000 14 | HOST = os.getenv("HOST", "localhost") 15 | PORT = int(os.getenv("PORT", "3000")) 16 | 17 | # Load the Stytch credentials, but quit if they aren't defined 18 | STYTCH_PROJECT_ID = os.getenv("STYTCH_PROJECT_ID") 19 | STYTCH_SECRET = os.getenv("STYTCH_SECRET") 20 | if STYTCH_PROJECT_ID is None: 21 | sys.exit("STYTCH_PROJECT_ID env variable must be set before running") 22 | if STYTCH_SECRET is None: 23 | sys.exit("STYTCH_SECRET env variable must be set before running") 24 | 25 | # NOTE: Set environment to "live" if you want to hit the live api 26 | stytch_client = stytch.Client( 27 | project_id=STYTCH_PROJECT_ID, 28 | secret=STYTCH_SECRET, 29 | environment="test", 30 | ) 31 | 32 | # create a Flask web app 33 | app = Flask(__name__) 34 | app.secret_key = 'some-secret-key' 35 | 36 | 37 | # handles the homepage for Hello Socks 38 | @app.route("/") 39 | async def index() -> str: 40 | user = await get_authenticated_user() 41 | if user is None: 42 | return render_template("loginOrSignUp.html") 43 | 44 | return render_template("loggedIn.html", email=user.emails[0].email) 45 | 46 | 47 | # takes the email entered on the homepage and hits the stytch 48 | # loginOrCreateUser endpoint to send the user a magic link 49 | @app.route("/login_or_create_user", methods=["POST"]) 50 | async def login_or_create_user() -> str: 51 | resp = await stytch_client.magic_links.email.login_or_create_async( 52 | email=request.form["email"] 53 | ) 54 | 55 | if resp.status_code != 200: 56 | print(resp) 57 | return "something went wrong sending magic link" 58 | return render_template("emailSent.html") 59 | 60 | 61 | # This is the endpoint the link in the magic link hits. 62 | # It takes the token from the link's query params and hits the 63 | # stytch authenticate endpoint to verify the token is valid 64 | @app.route("/authenticate") 65 | async def authenticate() -> str: 66 | resp = await stytch_client.magic_links.authenticate_async(request.args["token"], session_duration_minutes=60) 67 | 68 | if resp.status_code != 200: 69 | print(resp) 70 | return "something went wrong authenticating token" 71 | session["stytch_session_token"] = resp.session_token 72 | return redirect(url_for("index")) 73 | 74 | # handles the logout endpoint 75 | @app.route("/logout") 76 | async def logout() -> str: 77 | session.pop("stytch_session_token", None) 78 | return render_template("loggedOut.html") 79 | 80 | # Helper method for session authentication 81 | async def get_authenticated_user(): 82 | stytch_session = session.get("stytch_session_token") 83 | if not stytch_session: 84 | return None 85 | resp = await stytch_client.sessions.authenticate_async(session_token=stytch_session) 86 | if resp.status_code != 200: 87 | return None 88 | return resp.user 89 | 90 | # run's the app on the provided host & port 91 | if __name__ == "__main__": 92 | # in production you would want to make sure to disable debugging 93 | app.run(host=HOST, port=PORT, debug=True) 94 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask[async]~=2.2 2 | python-dotenv~=0.20 3 | stytch~=7.6 4 | -------------------------------------------------------------------------------- /static/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 | -------------------------------------------------------------------------------- /static/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-python-magic-links/f54d88255948d8e3672ba386b969225ce52643c9/static/images/arrow.png -------------------------------------------------------------------------------- /static/images/hellosocks-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-python-magic-links/f54d88255948d8e3672ba386b969225ce52643c9/static/images/hellosocks-background.png -------------------------------------------------------------------------------- /static/images/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-python-magic-links/f54d88255948d8e3672ba386b969225ce52643c9/static/images/person.png -------------------------------------------------------------------------------- /static/images/shopping-bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stytchauth/stytch-python-magic-links/f54d88255948d8e3672ba386b969225ce52643c9/static/images/shopping-bag.png -------------------------------------------------------------------------------- /templates/emailSent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Check your inbox (including the promotions folder)
for the login link.
Didn't find it? Send it again.
29 |Your socks just can't wait to be shipped!
28 | 34 |We'll see you next time.
28 |Sign up or log in to place your order.
No password required!