@@ -340,7 +340,127 @@ will only check refresh tokens, and 'all' which will check refresh and access to
340340to 'refresh'
341341
342342### Blacklist and Token Revoking
343- TODO
343+ This supports blacklisting and token revoking out of the box. This will allow you
344+ to revoke a specific token so a user can no longer access your endpoints, without
345+ having to change your secret key and thus revoke all the users tokens. In order
346+ to revoke a token, we need some storage where we can save a list of all the tokens
347+ we have created, as well as if they have been blacklisted or not. In order to make
348+ the underlying storage as agnostic as possible, we use [ simplekv] (http://pythonhosted.org/simplekv/ )
349+ to provide assess to a variaty of backends.
350+
351+ In production, it is important to use a backend that can have some sort of
352+ persistent storage, so we don't forget that we revoked a token, as well as
353+ something that can be safely used by the multiple thread and processes running
354+ your application. At present we believe redis is a good fit for this (it has the
355+ added benefit of removing expired tokens from the store automatically, so it
356+ wont blow up into something huge). But the choice is of course yours.
357+
358+ We also have to make a choice of if we want to check the blacklist against all
359+ requests, or only against refresh token requests. There are pros and cons to either
360+ way (extra overhead on jwt_required endpoints vs someone being able to use an
361+ access token freely until it expires). In this example, we are going to only check
362+ refresh tokens, and set the access tokes to a small expires time to help minimize
363+ damange that could be done.
364+ ``` python
365+ from datetime import timedelta
366+
367+ import simplekv
368+ import simplekv.memory
369+ from flask import Flask, request, jsonify
370+
371+ from flask_jwt_extended import JWTManager, jwt_required, \
372+ get_jwt_identity, revoke_token, unrevoke_token, \
373+ get_stored_tokens, get_all_stored_tokens, create_access_token, \
374+ create_refresh_token, jwt_refresh_token_required
375+
376+ # Setup flask
377+ app = Flask(__name__ )
378+ app.secret_key = ' super-secret'
379+
380+ # Configure access token expires time
381+ app.config[' JWT_ACCESS_TOKEN_EXPIRES' ] = timedelta(minutes = 5 )
382+
383+ # Enable and configure the JWT blacklist / token revoke. We are using an in
384+ # memory store for this example. In production, you should use something
385+ # else (csuch as redis, memcached, sqlalchemy). See here for options:
386+ # http://pythonhosted.org/simplekv/
387+ app.config[' JWT_BLACKLIST_ENABLED' ] = True
388+ app.config[' JWT_BLACKLIST_STORE' ] = simplekv.memory.DictStore()
389+ app.config[' JWT_BLACKLIST_TOKEN_CHECKS' ] = ' refresh'
390+
391+ jwt = JWTManager(app)
392+
393+
394+ @app.route (' /login' , methods = [' POST' ])
395+ def login ():
396+ username = request.json.get(' username' , None )
397+ password = request.json.get(' password' , None )
398+ if username != ' test' and password != ' test' :
399+ return jsonify({" msg" : " Bad username or password" }), 401
400+
401+ ret = {
402+ ' access_token' : create_access_token(identity = username),
403+ ' refresh_token' : create_refresh_token(identity = username)
404+ }
405+ return jsonify(ret), 200
406+
407+
408+ @app.route (' /refresh' , methods = [' POST' ])
409+ @jwt_refresh_token_required
410+ def refresh ():
411+ current_user = get_jwt_identity()
412+ ret = {
413+ ' access_token' : create_access_token(identity = current_user)
414+ }
415+ return jsonify(ret), 200
416+
417+
418+ # Endpoint for listing tokens that have the same identity as you
419+ @app.route (' /auth/tokens' , methods = [' GET' ])
420+ @jwt_required
421+ def list_identity_tokens ():
422+ username = get_jwt_identity()
423+ return jsonify(get_stored_tokens(username)), 200
424+
425+
426+ # Endpoint for listing all tokens. In your app, you should either not expose
427+ # this, or put some addition security on top of it so only trusted users,
428+ # (administrators, etc) can access it
429+ @app.route (' /auth/all-tokens' )
430+ def list_all_tokens ():
431+ return jsonify(get_all_stored_tokens()), 200
432+
433+
434+ # Endpoint for revoking a token
435+ @app.route (' /auth/tokens/revoke/<string:jti>' , methods = [' PUT' ])
436+ @jwt_required
437+ def change_jwt_revoke_state (jti ):
438+ try :
439+ revoke_token(jti)
440+ return jsonify({" msg" : " Token successfully revoked" }), 200
441+ except KeyError :
442+ return jsonify({' msg' : ' Token not foun' }), 404
443+
444+
445+ # Endpoint for un-revoking a token
446+ @app.route (' /auth/tokens/unrevoke/<string:jti>' , methods = [' PUT' ])
447+ @jwt_required
448+ def change_jwt_revoke_state (jti ):
449+ try :
450+ unrevoke_token(jti)
451+ return jsonify({" msg" : " Token successfully unrevoked" }), 200
452+ except KeyError :
453+ return jsonify({' msg' : ' Token not foun' }), 404
454+
455+
456+ @app.route (' /protected' , methods = [' GET' ])
457+ @jwt_required
458+ def protected ():
459+ return jsonify({' hello' : ' world' })
460+
461+ if __name__ == ' __main__' :
462+ app.run()
463+ ```
344464
345465
346466# Testing and Code Coverage
350470```
351471
352472# Documentation
353- Readthedocs coming soon!
473+ Readthedocs coming soon(tm) !
0 commit comments