Many internet services use OAuth and email based protocols for sending login links and tokens.
I think it's a pretty slick way to log users in and reduces overhead for them by adding yet another credential to their password managers.
I hadn't found a python implementation I liked so I decided to implement a login credential issuer in pure python that I could use to send login links to users, similar to a slack-like login flow.
It didn't take much code to implement! I'll walk you through it.
I started by defining a basic class that could generate and validate a token.
Pypale stands for (Py)thon (Pa)ssword(le)ss.
import base64
import logging
import random
import string
import time
import jwt
class Pypale:
JWT_ALGORITHM = "HS256"
ENCODING = "utf8"
def __init__(self, token_ttl_minutes: int, base_url: str, secret_key: str,
token_issue_ttl_seconds: int):
self.token_ttl_minutes = token_ttl_minutes
self.base_url = base_url
self.secret_key = secret_key
self.token_issue_ttl_seconds = token_issue_ttl_seconds
def generate_token(self):
pass
def validate_token(self):
pass
Next I just filled in jwt building and field setting for building the token.
def generate_token(self, email: str) -> str:
return base64.b64encode(
jwt.encode(self.generate_token_metadata(email),
self.secret_key,
algorithm=self.JWT_ALGORITHM)).decode(self.ENCODING)
def generate_token_metadata(self, email: str) -> dict:
return {
"sub": email,
"jti": self.one_time_nonce(),
"iat": int(time.time()),
"exp": int(time.time()) + (self.token_ttl_minutes * 60),
"iss": self.base_url
}
def one_time_nonce(
self,
size=16,
chars=string.ascii_letters + string.digits + "-") -> str:
return "".join(random.choice(chars) for _ in range(size))
What's a nonce? Read this.
Then I wrote a validator. I'm basically checking that the link click event occurs before the
link expiry event, set by the iat
+ token_issue_ttl_seconds
and that the user we're checking for
is the user that's mentioned in the token.
def valid_token(self, return_token: str, return_email: str = "") -> bool:
try:
decoded_return_token = base64.b64decode(return_token).decode(
self.ENCODING)
token_metadata = jwt.decode(decoded_return_token,
self.secret_key,
algorithms=[self.JWT_ALGORITHM])
if (token_metadata["iat"] + self.token_issue_ttl_seconds) < int(
time.time()):
logging.warning("Token was issued too long ago.")
return False
elif return_email != "":
if token_metadata["sub"] != return_email:
logging.warning("Token is not issued to the right user.")
return False
return True
else:
return True
except Exception as e:
logging.exception(
f"Raised exception while validating login link: {e}")
return False
And that's pretty much it! I recently published this code for you to use in whatever project you want to integrate passwordless logins into.
You can checkout the package on github at github.com/anthonycorletti/pypale and you can install it
by running pip install pypale
.
If you were following along and something didn't look quite right, let me know; a twitter dm works just fine – and feel free to open a pull request or issue if you'd like to contribute to pypale.