Authtopus is a python authorization library for use with Google Cloud Endpoints (GCE). The library boasts the following features:
- Standard username + password registrations and logins
- Registrations and logins via social accounts (only Facebook and Google currently supported, but more social providers could be supported in the future)
- Registrations via different methods with matching verified email address are merged into one user account
- Optional access tokens to restrict user registration to clients with valid tokens (for example, beta keys)
While GCE does have authentication built-in, as of writing this document GCE authentication only supports registration / login using Google accounts. Authtopus, on the other hand, supports much more.
Check out the library in action in this very simple Authtopus Example app, along with the code that runs the app. If you think you might like to use this library in a new or existing project, then read on. Note that I am by no means a web security expert and while I believe the library provides secure authentication, I cannot guarantee that I didn't miss something so the library is use at your own risk.
To demonstrate basic usage of Authtopus on the frontend, we provide code examples using AngulasJS's $http service. A new user registers by submitting a POST request to the /auth/v1.0/register
endpoint with their requested credentials:
$http.post( 'https://<name_of_site>/_ah/api/auth/v1.0/register', {
username: 'MrCool',
password: 'iamCooL',
email: '[email protected]',
verification_url: '<url_sent_to_users_email_address_to_verify_email>',
data: '<application_specific_data_forwarded_upon_successful_register>'
} );
Upon successful registration, an email is sent to the provided email address asking the user to verify their email address by proceeding to the verification url provided in the POST request above. In addition, after successful registration, the user can then login by sending a POST request to the /auth/v1.0/login
endpoint with their credentials:
$http.post( 'https://<name_of_site>/_ah/api/auth/v1.0/login', {
username_or_email: 'MrCool',
password: 'iamCooL'
} );
Alternatively, a user can login using their Facebook or Google account by sending a POST request to the /auth/v1.0/social_login
endpoint. A new user will be registered automatically if this is their first time logging in:
$http.post( 'https://<name_of_site>/_ah/api/auth/v1.0/social_login', {
access_token: <access_token_from_social_provider>,
provider: 'Facebook'
} );
Upon successful login, the server responds with a user_id_auth_token
string and a user
object containing the user's username, email and information indicating if the email has been verified. In order to authenticate future requests, the user must set the Authorization
header to the received user_id_auth_token
value. For example, the user can request another verification email be sent by sending an authorized POST request to the auth/v1.0/verify_email
endpoint:
$http.defaults.headers.common['Authorization'] = <user_id_auth_token>;
$http.post( 'https://<name_of_site>/_ah/api/auth/v1.0/verify_email', {
username: 'MrCool',
verification_url: '<url_sent_to_users_email_address_to_verify_email>'
} );
On the server side, new endpoints can retrieve the authenticated user as follows:
from endpoints import UnauthorizedException
from authtopus.api import Auth
def myApiMethod( self, ... ):
user = Auth.get_current_user( verified_email_required=True )
if user is None:
raise UnauthorizedException( 'Invalid credentials' )
# user authentication successful, so continue with stuff...
- The recommended method for obtaining Authtopus is to add the library as a submodule to an existing project. For example, given that you already have a directory called
ext
that holds your third-party modules (or given that you have just created such a directory), you can then run:
git submodule add https://github.com/rggibson/Authtopus.git ext/authtopus
Adding the project as a submodule allows you to pull for new updates to the library when they become available. Alternatively, you can simply copy the authtopus directory anywhere into your project and then copy a new, updated version when necessary.
Authtopus also depends on the endpoints-proto-datastore library. This library must also be added to your project:
git submodule add https://github.com/GoogleCloudPlatform/endpoints-proto-datastore.git ext/endpoints_proto_datastore
- Add the endpoints_proto_datastore and authtopus directories to your path by adding the following to appengine_config.py in your project's root directory (or create the file if it does not currently exist):
import os
import sys
ENDPOINTS_PROJECT_DIR = os.path.join( os.path.dirname( __file__ ),
'ext/endpoints_proto_datastore' )
AUTHTOPUS_PROJECT_DIR = os.path.join( os.path.dirname( __file__ ),
'ext/authtopus' )
sys.path.extend( [ ENDPOINTS_PROJECT_DIR, AUTHTOPUS_PROJECT_DIR,
# Other directories if you have more...
] )
- In
main.py
in your project's root directory, add the following lines if not already present:
import endpoints
import webapp2
from authtopus.api import Auth
from authtopus.cron import CleanupTokensHandler, CleanupUsersHandler
API = endpoints.api_server( [ Auth,
# Other APIs here...
], restricted=False )
CRON = webapp2.WSGIApplication(
[ ( '/cron/auth/cleanup-token/?', CleanupTokensHandler ),
( '/cron/auth/cleanup-users/?', CleanupUsersHandler ), ]
)
- In
app.yaml
add the following handlers and libraries if not already present:
handlers:
- url: /_ah/spi/.*
script: main.API
- url: /cron/.*
script: main.CRON
login: admin
libraries:
- name: pycrypto
version: latest
- name: endpoints
version: 1.0
- name: webapp2
version: 2.5.2
- In
cron.yaml
add the following handlers (create the file in your project's main directory if it does not already exist). You can skip the optional handler if you like:
cron:
- description: clean up expired tokens
url: /cron/auth/cleanup-tokens
schedule: every 24 hours
# Optional cron handler that occasionally deletes inactive users with no verified email
- description: clean up unverified, inactive users
url: /cron/auth/cleanup-users
schedule: every 168 hours
Feel free to change the schedule to however often you like.
And that's it! Well, there's actually one more step required to get email verification and password reset emails working in production. See the configuration section below for more details on that.
-
GET:
/auth/v1.0/current_user
- Retrieves the currently authenticated userRequest fields:
Response fields:
email_verified
- The verified email of the user, if anyemail_pending
- The pending email of the user. May be same as email_verified.username
- The username of the userhas_password
- Boolean indicating if the user can login via username and password (as opposed to only via social login)
Errors:
- 401
UnauthorizedException
- Occurs if the authorization token provided is invalid.
-
GET:
/auth/v1.0/get_user
- Retrieves a user by usernameRequest fields:
username
- The username of the user object we are requesting
Response fields:
email_verified
- The verified email of the user, if anyemail_pending
- The pending email of the user. May be same as email_verified.username
- The username of the userhas_password
- Boolean indicating if the user can login via username and pasword (as opposed to only via social login)
Errors:
- 400
BadRequestException
- Occurs if an invalid username is provided - 401
UnauthorizedException
- Occurs if user is not logged in - 403
ForbiddenException
- Occurs if the requested username is not the username of the currently authenticated user and the currently authenticated user is not a mod (users can be assigned mod status in the datastore viewer of the developer's console) - 404
NotFoundException
- Occurs if no user exists with the requested username
-
POST:
/auth/v1.0/update_user
- Updates information for the user with the requestedold_username
Request fields:
old_username
- The username of the user to updateemail
- The new email for the userusername
- The new username for the userold_password
- The user's old password (empty to leave password unchanged)password
- The user's new passwordverification_url
- URL sent to the user's new email address where the user is directed to go to verify their new email address if necessary
Response fields:
email
- The new email for the userusername
- The new username for the userverification_email_sent
- True if a verification email was sent to the new email address, False otherwise
Errors:
- 400
BadRequestException
- Occurs if any of the request parameters are invalid. The request parameters that are invalid are indicated in the error message, separated by '|' characters, where each part of the message is of the form<field>:<invalid reason>
. - 401
UnauthorizedException
- Occurs if the user is not currently logged in - 403
ForbiddenException
- Occurs if the requested old_username is not the username of the currently authenticated user and the currently authenticated user is not a mod - 409
ConflictException
- Occurs if the new email or username are already in use by another User. The error message indicates which fields are conflicted, separated by a '|' character.
-
POST:
/auth/v1.0/register
- Registers a new userRequest and response fields:
email
- The user's emailusername
- The user's usernamepassword
- The user's passwordaccess_token
- An access token that may be required for the user to register. Ignored ifconfig.USE_ACCESS_TOKENS
isFalse
.verification_url
- URL sent to the user's email address where the user is directed to go to verify their email addressdata
- Stringified application-specific data that is passed along after a successful registration (seecustom.user_created
in the Configuration section below).
Errors:
- 400
BadRequestException
- Occurs if any of the request parameters are invalid. The request parameters that are invalid are indicated in the error message, separated by '|' characters, where each part of the message is of the form<field>:<invalid reason>
. - 409
ConflictException
- Occurs if the email or username are already in use by another User. The error message indicates which fields are conflicted, separated by a '|' character.
-
POST:
/auth/v1.0/login
- Logs a user inRequest fields:
username_or_email
- The username or email of the user logging inpassword
- The password of the user logging in
Response fields:
user_id_auth_token
- Token that should theAuthorization
header should be set to in order to authenticate all future requests.user
- User object for the logged in user containing the same fields as the response fields of/auth/v1.0/current_user
.
Errors:
- 400
BadRequestException
- Occurs if invalid credentials are provided - 409
ConflictException
- Occurs in rare case when creating an authorization token fails.
-
POST:
/auth/v1.0/social_login
- Logs a user in via a social provider, registering the user in the process if necessaryRequest and response fields:
access_token
- Access token from the social providerprovider
- The social provider. Currently only 'Facebook' and 'Google' are supported.password
- The user's password, if anyregister_new_user
- Whether to register a new user or not, defaults to Trueauthtopus_access_token
- An access token that may be required for a new user to login. Ignored whenconfig.USE_ACCESS_TOKENS
isFalse
.
Response fields:
user_id_auth_token
- Token that should theAuthorization
header should be set to in order to authenticate all future requests.user
- User object for the logged in user containing the same fields as the response fields of/auth/v1.0/current_user
.password_required
- Boolean value indicating whether a password is required to complete the login. When true, nouser_id_auth_token
oruser
will be provided in the response. This field will only be true when a user has already registered with a verified email address that matches the verified email address from the social provider, and that user has a password for logging in. In this case, the user should resend the request with the user's password. This will merge the user's social id with their User account and future social logins will not require a password.
Errors:
- 400
BadRequestException
- Occurs when a bad access token or provider is given,register_new_user
is False and no matching user has registered yet, or authtopus is configured to use access tokens, a new user attemtpted to login, but the access token was invalid. - 401
UnauthorizedException
- Occurs when attempting to merge a social login with a username and password login for the first time, but the provided password is invalid. - 409
ConflictException
- Occurs when email is already in use by another user, but that user has not verified the email address. Also occurs in other rare instances when authtopus fails to set a unique username for the User or fails to generate an authorization token.
-
POST:
/auth/v1.0/logout
- Logs the currently logged in user outRequest and response fields:
Errors:
- 401
UnauthorizedException
- Occurs when no valid authorization token is provided
- 401
-
POST:
/auth/v1.0/send_email_verification
- Sends a new email requesting verification of the user's email addressRequest fields:
username
- The username of the user to send the email toverification_url
- URL sent to the user's email address where the user is directed to go to verify their email address. Atoken
parameter will be appended to this URL .
Response fields:
email
- The email address that the verification email was sent to
Errors:
- 400
BadRequestException
- Occurs when no verification url is provided - 401
UnauthorizedException
- Occurs when the user is not logged in - 403
ForbiddenException
- Occurs when the requestedusername
does not belong to the currently authorized user and the currently authenticated user is not a mod - 409
ConflictException
- Occurs when too many verification emails have recently been sent to the user's email address (see Configuration section below)
-
POST:
/auth/v1.0/verify_email
- Verify a user's email addressRequest and response fields:
token
- Verification token that was appended to theverification_url
when sent
Erros:
- 400
BadRequestException
- Occurs when token is invalid - 401
UnauthorizedException
- Occurs when no user is authenticated
-
POST:
/auth/v1.0/password_reset
- Sends a password reset emailRequest and response fields:
email
- The email to send the password reset information toset_password_url
- URL that the user is directed to proceed to in order to reset their password. Auser_id
andtoken
are appended as parameters to the URL that the user requires to set a new password.
Errors:
- 400
BadRequestException
- Occurs when no user currently registered with the provided email or noset_password_url
is provided - 409
ConflictException
- Occurs when sending email fails
-
POST:
/auth/v1.0/set_password
- Sets a new password for the userRequest and response fields:
new_password
- The new password for the useruser_id
- The id of the user requesting a new passwordtoken
- Password reset token from the password reset email
Errors:
- 400
BadRequestException
- Occurs whennew_password
does not meet the required criteria (4-20 characters in length) - 409
ForbiddenException
- Occurs when an invalid token is provided
-
POST:
/auth/v1.0/generate_access_tokens
- Generates a new set of access tokens (mods only)Request fields:
num_tokens
- The number of tokens to generate
Response fields:
access_tokens
- List of generated access tokens
Errors:
- 401
UnauthorizedException
- Occurs when the user is not logged in - 403
ForbiddenException
- Occurs when the user is not a mod
There are two ways to retrieve users from the server in python. The first is described above in the Basic Usage section by using the Auth.get_current_user
function. The second way is to retrieve a specifc user by username:
from endpoints import UnauthorizedException
from authtopus.api import Auth
def myApiMethod( self, ... ):
username = # ...
user = Auth.protected_get_user_by_username( username )
if user is None:
# Occurs when the current authenticated user's username is not
# username and the current authenticated user is not a mod
raise UnauthorizedException( 'Invalid credentials' )
# Retrieved user successfully. Do stuff...
There are a few configuration options available in authtopus/config.py
. Firstly, you can edit the email sender, subject, and body of emails that are sent for both email verification and password resets. Note that for email to work in production, config.EMAIL_SENDER
must be set to a valid sender address according to the GAE mail documentation. Emails are also logged at the DEBUG level, so you can see emails in your log when running the development server with the --log_level debug
option.
Secondly, the lifespan of various tokens can be set by modifying the values in config.TOKEN_LIFE_HOURS
. Note that auth tokens should have a short lifespan so that if the token happens to be compromised, an attacker will only have access for a short period of time.
Thirdly, there are also limits to how many unexpired password reset and verify email tokens a user can have, as well as the maximum number of tokens and user to potentially delete each time the respective cron job runs. When employing the cron job to delete inactive users with no verified email, the amount of inactivity time can be set by the config.UNVERIFIED_USER_LIFE_HOURS
parameter.
Fourthly, custom.user_created
can take in custom code to be run after a new
user has been created. This can allow your application to perform extra actions
that may be necessary to take once a new user has been created. For example,
you may want to create a user profile with additional,
non-authentication-related properties and associate that profile with the new
user's id.
Finally, access tokens can be enabled by setting config.USE_ACCESS_TOKENS
to True
. With access tokens on, user registration will fail without a valid token. To generate new tokens, a mod must submit a POST request to the /auth/v1.0/generate_access_tokens
endpoint. Tokens are invalidated once a user successfully registers with one.
- Email: [email protected]
- Twitter: @RichardGGibson