Django Rest Framework JWT: How to change the token expiration time when logged in - angularjs

I'm using Django REST framework JWT Auth for session creation and permissions, the only problem is: when I log in and after the token expires I can't continue doing the operation I want, unless I log in again. And I didn't fully understand the documentations provided for the additional settings.
So can any one explain a method for dynamically creating (and refreshing) my token (following best practices) so that I can keep doing operations when I'm logged in.
P.S: I'm using angular 2 for my front end, and I'm inserting the token in the Http requests headers. Thanks.

JWT token refresh is a little confusing, and i hope this explanation helps.
tokens have an issued at time (iat in the token)
tokens have an expiration date (now() + 1 hour, for example)
the token can't be changed. server can only issue a new one
iat never changes, but expires does change with each refresh
When you want to extend a token, this is what happens:
You send your token to the server endpoint /.../refresh/
Server checks its not expired: now() <= token.iat + JWT_REFRESH_EXPIRATION_DELTA
If not expired:
Issue a NEW token (returned in the json body, same as login)
New Token is valid for now() + JWT_EXPIRATION_DELTA
The issued at value in the token does not change
App now has 2 tokens (technically).
App discards the old token and starts sending the new one
If expired: return error message and 400 status
Example
You have EXPIRATION=1 hour, and a REFRESH_DELTA=2 days. When you login you get a token that says "created-at: Jun-02-6pm". You can refresh this token (or any created from it by refreshing) for 2 days. This means, for this login, the longest you can use a token without re-logging-in, is 2 days and 1 hour. You could refresh it every 1 second, but after 2 days exactly the server would stop allowing the refresh, leaving you with a final token valid for 1 hour. (head hurts).
Settings
You have to enable this feature in the backend in the JWT_AUTH settings in your django settings file. I believe that it is off by default. Here are the settings I use:
JWT_AUTH = {
# how long the original token is valid for
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=2),
# allow refreshing of tokens
'JWT_ALLOW_REFRESH': True,
# this is the maximum time AFTER the token was issued that
# it can be refreshed. exprired tokens can't be refreshed.
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
Then you can call the JWT refresh view, passing in your token in the body (as json) and getting back a new token. Details are in the docs at http://getblimp.github.io/django-rest-framework-jwt/#refresh-token
$ http post localhost:8000/auth/jwt/refresh/ --json token=$TOKEN
Which returns:
HTTP 200
{
"token": "new jwt token value"
}

I've had same problem in angularjs and I've solved it by writing a custom interceptor service for my authentication headers.
Here's my code:
function($http, $q, store, jwtHelper) {
let cache = {};
return {
getHeader() {
if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {
return $q.when({ 'Authorization': 'Token ' + cache.access_token });
} else {
cache.access_token = store.get('token');
if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {
return $q.when({ 'Authorization': 'Token ' + cache.access_token });
} else {
return $http.post(localhost + 'api-token-refresh/',{'token': cache.access_token})
.then(response => {
store.set('token', response.data.token);
cache.access_token = response.data.token;
console.log('access_token', cache.access_token);
return {'Authorization': 'Token ' + cache.access_token};
},
err => {
console.log('Error Refreshing token ',err);
}
);
}
}
}
};
}
Here, on every request I've had to send, the function checks whether the token is expired or not.
If its expired, then a post request is sent to the "api-token-refresh" in order to retrieve the new refreshed token, prior to the current request.
If not, the nothing's changed.
But, you have to explicitly call the function getHeader() prior to the request to avoid circular dependency problem.
This chain of requests can be written into a function like this,
someResource() {
return someService.getHeader().then(authHeader => {
return $http.get(someUrl, {headers: authHeader});
});
}

just add this line to your JWT_AUTH in settings.py file:
'JWT_VERIFY_EXPIRATION': False,
it worked for me.

Related

how to store bearer token in cookies in react js frontend

I am using React JS for the front-end part of my code. I want to store the bearer token in cookies and then return the bearer token in the content field when the API is called successfully. As I haven't used cookies earlier so want to know how I can accomplish this task. Also the back-end part is not done by me.
Following is the code in which I am calling the API
onSubmitSignup = () => {
fetch('https://cors-anywhere.herokuapp.com/http://35.154.16.105:8080/signup/checkMobile',{
method:'post',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({
mobile:this.state.mobile
})
})
.then(response => response.json())
.then(data =>{
if(data.statusCode === '2000'){
localStorage.setItem('mobile',this.state.mobile);
// this.props.loadNewUser(this.state.mobile);
this.props.onRouteChange('otp','nonav');
}
})
// this.props.onRouteChange('otp','nonav');
}
First of all, on this line:
if(data.statusCode === '2000'){
Are you sure the status code shouldn't be 200 and not 2000.
Secondly, there are packages for managing cookies. One that springs to mind is:
Link to GitHub "Universal Cookie" repo
However you can use vanilla js to manage cookies, more info can be found on the Mozilla website:
Here
When you make that initial API call, within the data returned, I assume the Bearer token is returned too. Initialise the cookie there like so:
document.cookie = "Bearer=example-bearer-token;"
When you need access to the cookie at a later date, you can just use the following code:
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('Bearer'))
.split('=')[1];
And then forward the bearer with the next call.
Edit
Set the cookie bearer token like this:
document.cookie = "Bearer=example-bearer-token;"
Get the cookie bearer token like this:
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('Bearer'))
.split('=')[1];
A cookie is made up of key/value pairs separated by a semi-colon. Therefore the above code to get the "Bearer" value, firstly gets the cookie, splits it into its key/value pairs, finds the row that has a key of "Bearer" and splits that row to attain the Bearer token.
In your comment you say the dev team said the bearer will be in the "content". In your ajax request you already have access to that content through data. You need to debug that request to find out what it is coming back as. I assume you just need to grab the token from the returned data inside of the "If" block where you check for your statusCode.
It will be something like:
document.cookie = "Bearer=" + data.bearer;
However, I don't have the shape of your data so you can only work that final part out yourself.

How to tell if a user is logged in with http only cookies and JWT in react (client-side)

So I'm trying to follow the security best practices and I'm sending my JWT token over my React app in a only-secure http-only cookie.
This works fine for requests but the major issue I find with this approach is, how can I tell if the user is logged-in on client-side if I can't check if the token exists? The only way I can think of is to make a simple http to a protected endpoint that just returns 200.
Any ideas? (not looking for code implementations)
The approach I would follow is to just assume the user is logged in, and make the desired request, which will send the httpOnly token automatically in the request headers.
The server side should then respond with 401 if the token is not present in the request, and you can then react in the client side accordingly.
Using an endpoint like /api/users/me
Server-side
Probably you don't only need to know if a user is already logged in but also who that user is. Therefore many APIs implement an endpoint like /api/users/me which authenticates the request via the sent cookie or authorization header (or however you've implemented your server to authenticate requests).
Then, if the request is successfully authenticated, it returns the current user. If the authentication fails, return a 401 Not Authorized (see Wikipedia for status codes).
The implementation could look like this:
// UsersController.ts
// [...]
initializeRoutes() {
this.router.get('users/me', verifyAuthorization(UserRole.User), this.getMe);
}
async getMe(req: Request, res: Response) {
// an AuthorizedRequest has the already verified JWT token added to it
const { id } = (req as AuthorizedRequest).token;
const user = await UserService.getUserById(id);
if (!user) {
throw new HttpError(404, 'user not found');
}
logger.info(`found user <${user.email}>`);
res.json(user);
}
// [...]
// AuthorizationMiddleware.ts
export function verifyAuthorization(expectedRole: UserRole) {
// the authorization middleware throws a 401 in case the JWT is invalid
return async function (req: Request, res: Response, next: NextFunction) {
const authorization = req.headers.authorization;
if (!authorization?.startsWith('Bearer ')) {
logger.error(`no authorization header found`);
throw new HttpError(401, 'unauthorized');
}
const token = authorization.split(' ')[1];
const decoded = AuthenticationService.verifyLoginToken(token);
if (!decoded) {
logger.warn(`token not verified`);
throw new HttpError(401, 'unauthorized');
}
(req as AuthorizedRequest).token = decoded;
const currentRole = UserRole[decoded.role] ?? 0;
if (currentRole < expectedRole) {
logger.warn(`user not authorized: ${UserRole[currentRole]} < ${UserRole[expectedRole]}`);
throw new HttpError(403, 'unauthorized');
}
logger.debug(`user authorized: ${UserRole[currentRole]} >= ${UserRole[expectedRole]}`);
next();
};
}
Client-side
If the response code is 200 OK and contains the user data, store this data in-memory (or as alternative in the local storage, if it doesn't include sensitive information).
If the request fails, redirect to the login page (or however you want your application to behave in that case).

IBM Watson tokens expire in one hour. Code to refresh the tokens?

I'm setting up IBM Watson Speech-to-Text. This requires an access token, documented here:
"Tokens have a time to live (TTL) of one hour, after which you can no longer use them to establish a connection with the service. Existing connections already established with the token are unaffected by the timeout. An attempt to pass an expired or invalid token elicits an HTTP 401 Unauthorized status code from DataPower. Your application code needs to be prepared to refresh the token in response to this return code."
I don't see on the documentation page an example of application code to refresh the token.
Should my code generate a new token for every user who downloads the JavaScript app? Or should the server request a new token every hour, and give all users the same token for an hour?
The shell command to get a token is:
curl -X GET --user username:password \
--output token \
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api"
That looks like I can send an HTTP request from my JavaScript app and get back a token. Should I request the token as a file, and then have my app get the token from the file?
enter code hereWhat worked was to use Cloud Functions for Firebase (part of Google Cloud Functions), triggered on a user logging in, running Node to send an HTTP request to get the token and then write the result to an AngularJS value service.
This Cloud Function works:
// Node modules
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request'); // node module to send HTTP requests
const fs = require('fs');
admin.initializeApp(functions.config().firebase);
exports.getWatsonToken = functions.database.ref('userLoginEvent').onUpdate(event => { // authentication trigger when user logs in
var username = 'groucho',
password = 'swordfish',
url = 'https://' + username + ':' + password + '#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api';
request({url: url}, function (error, response, body) {
var tokenService = "app.value('watsonToken','" + body + "');";
fs.writeFile('../public/javascript/services/watsonTokenValue.js', tokenService, (err) => {
if (err) throw err;
console.log('The file has been saved!');
}); // close fs.writeFile
}); // close request
}); // close getWatsonToken
In the controller:
firebase.auth().onAuthStateChanged(function(user) { // this runs on login
if (user) { // user is signed in
console.log("User signed in!");
$scope.authData = user;
firebase.database().ref('userLoginEvent').update({'user': user.uid}); // update Firebase database to trigger Cloud Function to get a new IBM Watson token
} // end if user is signed in
else { // User is signed out
console.log("User signed out.");
}
}); // end onAuthStateChanged
Walking through the Cloud Function, it injects four Node modules, including request for sending HTTP requests, and fs for writing the results to a file. Then the trigger is set for an update to a location userLoginEvent in the Firebase database (which I created from the console). Next, the HTTP request goes out. The response (the token) is called body. app.value('watsonToken','" + body + "');" is an Angular value service to wrap the token. Then fs writes all this to a location in my project.
In the AngularJS controller, onAuthStateChanged triggers when a user logins in. The user.uid is then updated to the location userLoginEvent in the Firebase database, and the Cloud Function triggers, the HTTP request goes out, and the response is written to an Angular service.

Basic strategy for delegate refresh token to get new JWT

I have been doing pretty well implementing Angular SPA and JWT, but I always have a hard time with delegating for a new token.
My basic strategy has been:
In auth interceptor get Auth Error = > Delegate with refresh token, replace JWT, else logout
Which did not work because multiple Async calls would fire and one would get the delegate function, but then the refresh token would have been used for the second one and that one will fail, then the user would get logged out.
Before anything else: Check token expiration, if expired => delegate with refresh token, replace jwt, else logout
Which had a similar issue where the first call would notice it was expired, and go get the new token, but since it is Async, the rest of the calls would fire and fail etc.
What is the basic strategy here. I feel like the very first thing the app should do is check the JWT and delegate for a new one if it's a bad token, but in that case it should no be asynchronous. Do I just not delete the refresh token on use?
Any help would be great, I feel like this is the last major hole in my understanding. Thanks!
Try using Witold Szczerba's "http interceptor".
In a nutshell the first failed http call triggers and event and subsequent calls get pushed into an array. Upon the event firing you have a chance to do some logic and then replay the failed calls.
That said you probably should just do a token rotation before needing to actually use the refresh token. For example consider this code which could be added to this function
.config(function($httpProvider) {
$httpProvider.interceptors.push(function(moment, $rootScope, $q, httpBuffer) {
return {
request: function (config) {
if ($rootScope.authToken) {
config.headers["Authentication"] = 'Bearer ' + $rootScope.authToken;
var payload = angular.fromJson(atob($rootScope.authToken.split('.')[1]));
var utcNow = moment.utc().valueOf();
// 3600000 ms = 1 hr
if(utcNow > payload.iat + 3600000){
$rootScope.$broadcast('auth:rotateToken', $rootScope.authToken);
}
}
return config;
},
//responseError: ...see Witold's code...
});
})

AngularJS or SPA with JWT - expiry and refresh

I understand the flow of JWT and a single page application in terms of login and JWT issuance. However, if the JWT has a baked in expiry, AND the server isn't issuing a new JWT on each request, what is the best way for renewing? There is a concept of refresh tokens, but storing such a thing in a web browser sounds like a golden ticket.
IE I could easily go into a browsers local storage and steal a refresh token. Then I could go to another computer and issue myself a new token. I feel like there would need to be a server session in a db that's referenced in the JWT. Therefore the server could see if the session ID is still active or invalidated by a refresh token.
What are the secure ways to implement JWT in a SPA and handling new token issuance whilst the user is active?
Renewing the token every 15 minutes (if it lives for 30) works if you don't have another restriction in your server in which you need to check for 1 hour inactivity to log the user out. If you just want this short lived JWT and keep on updating it, it'd work.
I think one of the big advantages of using JWT is to actually NOT need a server session and therefore not use the JTI. That way, you don't need syncing at all so that'd be the approach I'd recommend you following.
If you want to forcibly logout the user if he's inactive, just set a JWT with an expiration in one hour. Have a $interval which every ~50 minutes it automatically gets a new JWT based on the old one IF there was at least one operation done in the last 50 minutes (You could have a request interceptor that just counts requests to check if he's active) and that's it.
That way you don't have to save JTI in DB, you don't have to have a server session and it's not a much worse approach than the other one.
What do you think?
I think for my implementation I'm going to go with, after a bit of search, is...
Use case:
JWT is only valid for 15 minutes
User session will timeout after 1 hour of inactivity
Flow:
User logs in and is issued a JWT
JWT has a 15 minute expiration with claim 'exp'
JWT JTI is recorded in db has a session of 1 hour
After a JWT expires (after 15 min):
Current expired JWT will be used # a /refresh URI to exchange for a new one. The expired JWT will only work at the refresh endpoint. IE API calls will not accept an expired JWT. Also the refresh endpoint will not accept unexpired JWT's.
JTI will be checked to see if its been revoked
JTI will be checked to see if its still within 1 hour
JTI session will be deleted from DB
New JWT will be issued and new JTI entry will be added to the db
If a user logs out:
JWT is deleted from client
JTI is deleted from db so JWT cannot be refreshed
With that said, there will be database calls every 15 minutes to check a JTI is valid. The sliding session will be extended on the DB that tracks the JWT's JTI. If the JTI is expired then the entry is removed thus forcing the user to reauth.
This does expose a vulnerability that a token is active for 15 minutes. However, without tracking state every API request I'm not sure how else to do it.
I can offer a different approach for refreshing the jwt token.
I am using Angular with Satellizer and Spring Boot for the server side.
This is the code for the client side:
var app = angular.module('MyApp',[....]);
app.factory('jwtRefreshTokenInterceptor', ['$rootScope', '$q', '$timeout', '$injector', function($rootScope, $q, $timeout, $injector) {
const REQUEST_BUFFER_TIME = 10 * 1000; // 10 seconds
const SESSION_EXPIRY_TIME = 3600 * 1000; // 60 minutes
const REFRESH_TOKEN_URL = '/auth/refresh/';
var global_request_identifier = 0;
var requestInterceptor = {
request: function(config) {
var authService = $injector.get('$auth');
// No need to call the refresh_token api if we don't have a token.
if(config.url.indexOf(REFRESH_TOKEN_URL) == -1 && authService.isAuthenticated()) {
config.global_request_identifier = $rootScope.global_request_identifier = global_request_identifier;
var deferred = $q.defer();
if(!$rootScope.lastTokenUpdateTime) {
$rootScope.lastTokenUpdateTime = new Date();
}
if((new Date() - $rootScope.lastTokenUpdateTime) >= SESSION_EXPIRY_TIME - REQUEST_BUFFER_TIME) {
// We resolve immediately with 0, because the token is close to expiration.
// That's why we cannot afford a timer with REQUEST_BUFFER_TIME seconds delay.
deferred.resolve(0);
} else {
$timeout(function() {
// We update the token if we get to the last buffered request.
if($rootScope.global_request_identifier == config.global_request_identifier) {
deferred.resolve(REQUEST_BUFFER_TIME);
} else {
deferred.reject('This is not the last request in the queue!');
}
}, REQUEST_BUFFER_TIME);
}
var promise = deferred.promise;
promise.then(function(result){
$rootScope.lastTokenUpdateTime = new Date();
// we use $injector, because the $http creates a circular dependency.
var httpService = $injector.get('$http');
httpService.get(REFRESH_TOKEN_URL + result).success(function(data, status, headers, config) {
authService.setToken(data.token);
});
});
}
return config;
}
};
return requestInterceptor;
}]);
app.config(function($stateProvider, $urlRouterProvider, $httpProvider, $authProvider) {
.............
.............
$httpProvider.interceptors.push('jwtRefreshTokenInterceptor');
});
Let me explain what it does.
Let's say we want the "session timeout" (token expiry) to be 1 hour.
The server creates the token with 1 hour expiration date.
The code above creates a http inteceptor, that intercepts each request and sets a request identifier. Then we create a future promise that will be resolved in 2 cases:
1) If we create for example a 3 requests and in 10 seconds no other request are made, only the last request will trigger an token refresh GET request.
2) If we are "bombarded" with request so that there is no "last request", we check if we are close to the SESSION_EXPIRY_TIME in which case we start an immediate token refresh.
Last but not least, we resolve the promise with a parameter. This is the delta in seconds, so that when we create a new token in the server side, we should create it with the expiration time (60 minutes - 10 seconds). We subtract 10 seconds, because of the $timeout with 10 seconds delay.
The server side code looks something like this:
#RequestMapping(value = "auth/refresh/{delta}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<?> refreshAuthenticationToken(HttpServletRequest request, #PathVariable("delta") Long delta, Device device) {
String authToken = request.getHeader(tokenHeader);
if(authToken != null && authToken.startsWith("Bearer ")) {
authToken = authToken.substring(7);
}
String username = jwtTokenUtil.getUsernameFromToken(authToken);
boolean isOk = true;
if(username == null) {
isOk = false;
} else {
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
isOk = jwtTokenUtil.validateToken(authToken, userDetails);
}
if(!isOk) {
Map<String, String> errorMap = new HashMap<>();
errorMap.put("message", "You are not authorized");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorMap);
}
// renew the token
final String token = jwtTokenUtil.generateToken(username, device, delta);
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
Hope that helps someone.

Resources