The One True Auth Server
Spoiler Alert: There isn't one
auth = authorization/authentication
- authentication = you are who you say you are
- authorization = you have permission to do something
Let's try to implement the simplest possible solution for this problem.
We'll use group membership as the mechanism for access. If you're in a group, you get access to those operations. If you're not in the group, you don't. Simple.
- /login: Authentication gives you a unique token
- /login/<email>/<password> -
- /login/<basic_auth> - basically the above but base64 MIME encoded. Used by HTTP Basic Authentication
- /valid(ate): Authorization tells you whether a user is a member of a group
- /valid/<token>/<group> - is token a member of group?
interesting stuff:
hardcoded logins: this is how a lot of projects starts out.
anonymous logins: this allows us to make users without a lot of fuss.
email logins: now we can use email as a way to authenticate users. Of course, we need a fully functional email system attached to do this.
social logins: with this, we can piggyback off of users' social logins.
Base Framework:
Python/Bottle
How do we store this stuff? In memory is probably the fastest/easiest but it's not persistent.
Hack: Persist records on every Create/Update/Delete, and read in all records when you start up. Sort of a poor mans' write-through cache.
Hardcoded:
#!/usr/bin/env python
import bottle,json,base64
app=bottle.Bottle()
# our in-memory db
app.UserList=[
{ "e":"j@x", "u":"u0", "t":"t1", "p": "a", "g": ["user","admin"] },
{ "e":"z@w", "u":"u1", "t":"t2", "p": "b", "g": ["admin","root"] },
{ "e":"q@q", "u":"u2", "t":"t3", "p": "c", "g": [], "disabled": True, }
]
def digest(u,p): return base64.b64encode(u+':'+p)
# indexes
app.L = dict( (digest(u['e'],u['p']),u) for u in app.UserList )
app.T = dict( (u['t'], u) for u in app.UserList )
# util funcs for error conditions
def AccessDenied():
raise bottle.HTTPResponse(status=403, body=json.dumps(dict(success=False,
reason="Access Denied")))
@app.hook('after_request')
def enable_cors():
bottle.response.headers['Access-Control-Allow-Origin'] = '*'
bottle.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
@app.get('/valid/<token>/<group>')
def is_valid(token,group=None):
value = app.T.get(token,None)
if not value: # you're not in the DB!
return AccessDenied()
if value.get('disabled',0): # you're disabled!
return AccessDenied()
if group and group not in value['g']: # are you in the group?
return AccessDenied()
return dict(result=True) # all tests pass, you're in
@app.get('/login/<username>/<password>')
@app.get('/login/<username_password>')
def login(username_password=None,username=None,password=None):
if username_password is None:
username_password = digest(username,password)
pass
value = dict( app.L.get(username_password,{}) )
if not value: # you're not in the DB!
return AccessDenied()
value.pop('p',0) # don't need to be sending the password around
value.pop('g',0) # or the groups
return dict(result=dict(authinfo=value))
if __name__=='__main__':
app.run(host='', port='9090')
So we got where we need to go in 40 lines of python (whitespace/comments not included).
Here's there gist of it over on github.
Things missing that would be nice to have:
- No way to add/edit/delete users except by changing the file and rebooting
- No way to allow logins from social
- A nice console