diff --git a/uffd/invite/models.py b/uffd/invite/models.py
index 196e333cd378f2a52e60104c1df87db47e29005f..1c54ba881b5401b6f022c2e96892ce026a144d7f 100644
--- a/uffd/invite/models.py
+++ b/uffd/invite/models.py
@@ -1,4 +1,3 @@
-import secrets
 import datetime
 
 from flask import current_app
@@ -9,6 +8,7 @@ from uffd.ldapalchemy.dbutils import DBRelationship
 from uffd.database import db
 from uffd.user.models import User
 from uffd.signup.models import Signup
+from uffd.utils import token_urlfriendly
 
 invite_roles = db.Table('invite_roles',
 	Column('invite_id', Integer(), ForeignKey('invite.id'), primary_key=True),
@@ -18,7 +18,7 @@ invite_roles = db.Table('invite_roles',
 class Invite(db.Model):
 	__tablename__ = 'invite'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
-	token = Column(String(128), unique=True, nullable=False, default=lambda: secrets.token_urlsafe(48))
+	token = Column(String(128), unique=True, nullable=False, default=token_urlfriendly)
 	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
 	creator_dn = Column(String(128), nullable=True)
 	creator = DBRelationship('creator_dn', User)
diff --git a/uffd/selfservice/models.py b/uffd/selfservice/models.py
index 15d7050f2bc60006506509ed91a76dd08faaaccd..b87148cd8dca5292b98f80d2059852ecbebf4ca4 100644
--- a/uffd/selfservice/models.py
+++ b/uffd/selfservice/models.py
@@ -1,15 +1,12 @@
 import datetime
-import secrets
 
 from sqlalchemy import Column, String, DateTime
 
 from uffd.database import db
-
-def random_token():
-	return secrets.token_hex(128)
+from uffd.utils import token_urlfriendly
 
 class Token():
-	token = Column(String(128), primary_key=True, default=random_token)
+	token = Column(String(128), primary_key=True, default=token_urlfriendly)
 	created = Column(DateTime, default=datetime.datetime.now)
 
 class PasswordToken(Token, db.Model):
diff --git a/uffd/session/models.py b/uffd/session/models.py
index c91619e6d830a27a238e975d6b014e010a705ef1..ab243e0fcdbac2bc6dd55e85371a93c06fdc857e 100644
--- a/uffd/session/models.py
+++ b/uffd/session/models.py
@@ -1,6 +1,5 @@
 import datetime
 import secrets
-import math
 import enum
 
 from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Enum
@@ -10,15 +9,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
 from uffd.ldapalchemy.dbutils import DBRelationship
 from uffd.database import db
 from uffd.user.models import User
-
-def token_typeable(nbytes=None):
-	'''Return random text token that is easy to type (on mobile)'''
-	alphabet = '123456789abcdefghkmnopqrstuvwx' # No '0ijlyz'
-	if nbytes is None:
-		nbytes = secrets.DEFAULT_ENTROPY
-	nbytes_per_char = math.log(len(alphabet), 256)
-	nchars = math.ceil(nbytes / nbytes_per_char)
-	return ''.join([secrets.choice(alphabet) for _ in range(nchars)])
+from uffd.utils import token_typeable
 
 # Device login provides a convenient and secure way to log into SSO-enabled
 # services on a secondary device without entering the user password or
diff --git a/uffd/signup/models.py b/uffd/signup/models.py
index 7c7f4cc9b476b5632100b4038bf26e341117264b..e994acb00ccf3701f73d144730c743b91910e8ee 100644
--- a/uffd/signup/models.py
+++ b/uffd/signup/models.py
@@ -1,4 +1,3 @@
-import secrets
 import datetime
 from crypt import crypt
 
@@ -8,6 +7,7 @@ from uffd.ldapalchemy.dbutils import DBRelationship
 from uffd.database import db
 from uffd.ldap import ldap
 from uffd.user.models import User
+from uffd.utils import token_urlfriendly
 
 class Signup(db.Model):
 	'''Model that represents a self-signup request
@@ -26,7 +26,7 @@ class Signup(db.Model):
 	As long as they are not completed, signup requests have no effect each other
 	or different parts of the application.'''
 	__tablename__ = 'signup'
-	token = Column(String(128), primary_key=True, default=lambda: secrets.token_urlsafe(48))
+	token = Column(String(128), primary_key=True, default=token_urlfriendly)
 	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
 	loginname = Column(Text)
 	displayname = Column(Text)
diff --git a/uffd/utils.py b/uffd/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff40711c3e114736781931aa83ee3c2cc10a20a9
--- /dev/null
+++ b/uffd/utils.py
@@ -0,0 +1,20 @@
+import secrets
+import math
+
+def token_with_alphabet(alphabet, nbytes=None):
+	'''Return random text token that consists of characters from `alphabet`'''
+	if nbytes is None:
+		nbytes = max(secrets.DEFAULT_ENTROPY, 32)
+	nbytes_per_char = math.log(len(alphabet), 256)
+	nchars = math.ceil(nbytes / nbytes_per_char)
+	return ''.join([secrets.choice(alphabet) for _ in range(nchars)])
+
+def token_typeable(nbytes=None):
+	'''Return random text token that is easy to type (on mobile)'''
+	alphabet = '123456789abcdefghkmnopqrstuvwx' # No '0ijlyz'
+	return token_with_alphabet(alphabet, nbytes=nbytes)
+
+def token_urlfriendly(nbytes=None):
+	'''Return random text token that is urlsafe and works around common parsing bugs'''
+	alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+	return token_with_alphabet(alphabet, nbytes=nbytes)