SSO example python 2.7 - webapp2, google app engine with simpleauth

(Paul Ceccato) #1

This is based on the django example. I have modified it to work with webapp2 on google app engine, using the simpleauth library for login management.

This decorator is used on get methods which require login. Once the user is logged in these fields will be set with the users details, which will be passed back to discourse


def simpleauth_login_required(handler_method):
	"""A decorator to require that a user be logged in to access a handler.

	To use it, decorate your get() method like this:

		def get(self):
			user = self.current_user
			self.response.out.write('Hello, ' +
	def check_login(self, *args, **kwargs):
		if self.request.method != 'GET':
			self.abort(400, detail='The login_required decorator can only be used for GET requests.')

		if self.logged_in:
			handler_method(self, *args, **kwargs)
			self.session['original_url'] = self.request.url

	return check_login

Here is the discourse SSO handler

    import base64
import hmac
import hashlib
import urllib

class DiscourseSSOHandler(BaseRequestHandler):

	def get(self):

		payload = self.request.get('sso')
		signature = self.request.get('sig')

		if None in [payload, signature]:
			self.abort(400, detail='No SSO payload or signature.')
		## Validate the payload

			payload = urllib.unquote(payload)
			assert 'nonce' in base64.decodestring(payload)
			assert len(payload) > 0
		except AssertionError:
			self.abort(400, detail='Invalid payload..')

		key = secrets.DISCOURSE_SSO_SECRET
		h =, payload, digestmod=hashlib.sha256)
		this_signature = h.hexdigest()

		if this_signature != signature:
			self.abort(400, detail='Payload does not match signature.')

		## Build the return payload

		params = {
			'nonce': base64.decodestring(payload).split('=')[1],
			'email': self.current_email,
			'external_id': self.current_id,
			'username': self.current_first_name

		return_payload = base64.encodestring(urllib.urlencode(params))
		h =, return_payload, digestmod=hashlib.sha256)
		query_string = urllib.urlencode({'sso': return_payload, 'sig': h.hexdigest()})

		## Redirect back to Discourse

		url = '{0}/session/sso_login'.format(secrets.DISCOURSE_BASE_URL)
		self.redirect('{0}?{1}'.format(url, query_string))