I'm using basic HTTP auth (without SSL for testing).
The login works fine, we send an $http request with the authorization header and if the login is correct, it works.
For logout, I'm setting the Authorization header to a bad value and sending an $http request to "trick" the server. The server seems to ignore the new changed auth header. I verified with developer tools in FF that the header value is all ones, but the request is still successful.
How do I "logout"? The logout function sends a bad Authorization header, and the browser sends it according to firebug. What's going on? This is a Java EE 7 app with Wildfly 9 and Shiro, if that makes a difference.
Here is the code:
var DataFactory = function($http, $rootScope, $base64, $cookieStore) {
var _debug = false;
function _d(message) {
if (!_debug) {
return;
}
console.log(message);
}
function setDebug(flag) {
_debug = flag;
}
function doLogout() {
_d("Logging out");
$rootScope.globals = {};
$cookieStore.remove('globals');
$http.defaults.headers.common['Authorization'] = 'Basic 111111111111111111';
$http.get(
'http://localhost:8080/myapp/rest/v1/svc')
.then(function(data) {
alert("Logout: " + JSON.stringify(data.data));
}, function(data) {
alert("Logout Error: " + JSON.stringify(data))
});
}
function doLogin(username, password) {
var token = $base64.encode(username + ":" + password);
_d("Logging " + username + " in with token " + token);
$http.defaults.headers.common['Authorization'] = 'Basic ' + token; // jshint
// ignore:line
$rootScope.globals = {
token : token,
username : username
};
$cookieStore.put("globals", $rootScope.globals);
_d("Login finished, globals are: " + JSON.stringify($rootScope.globals));
$http.get(
'http://localhost:8080/myapp/rest/v1/svc')
.then(function(data) {
alert(JSON.stringify(data.data));
}, function(data) {
alert("Error: " + JSON.stringify(data))
});
}
;
return {
setDebug : setDebug,
doLogin : doLogin,
doLogout : doLogout
};
}
Sending your own authorization string within a XHR request will not magically delete the information cached in the browser. Basic authentication has no concept of logging out. The only way to "logout" with basic authentication is to make the credentials invalid at the server, i.e. change username and/or password so that the stored credentials do not work any longer.
Related
I have spring boot backend app with Angular js app. The login process and initial backend communication are successful. After some idle time, the front end will show 403 forbidden with token not active on the backend console.
The code below contains refresh token, But it seems not working.
// use bearer token when calling backend
themesApp.config(['$httpProvider', function($httpProvider) {
var isExpired = keycloak.isTokenExpired();
var token = keycloak.token;
if (isExpired) {
keycloak.updateToken(5)
.success(function() {
$httpProvider.defaults.headers.common['Authorization'] = 'BEARER ' + token;
})
.error(function() {
console.error('Failed to refresh token');
});
}
$httpProvider.defaults.headers.common['Authorization'] = 'BEARER ' + token;
}]);
Error on the backend
2017-05-29 10:08:23.715 ERROR 5072 --- [nio-8080-exec-3] o.k.a.BearerTokenRequestAuthenticator : Failed to verify token
org.keycloak.common.VerificationException: Token is not active
Something must be wrong on the Keycloak Server, Token not active means token being is expired or is used before it gets valid. Could it be that the time/date is wrong on your KC server ?
you can config the 'Session Idle Time' here:
I had the same issue and handle it with an automatical logout. So the user has to login again.
In your code:
var token = keycloak.token;
you define the value of token once. After the update you have to set it again:
// use bearer token when calling backend
themesApp.config(['$httpProvider', function($httpProvider) {
var isExpired = keycloak.isTokenExpired();
var token = keycloak.token;
if (isExpired) {
keycloak.updateToken(5)
.success(function() {
// UPDATE THE TOKEN
token = keycloak.token;
$httpProvider.defaults.headers.common['Authorization'] = 'BEARER ' + token;
})
.error(function() {
console.error('Failed to refresh token');
});
}
$httpProvider.defaults.headers.common['Authorization'] = 'BEARER ' + token;
}]);
I'm fairly new to Angular and I'm trying to implement a mechanism for keeping active users logged in as long as they're active.
I have a token endpoint that issues a JWT token to a user
{
"access_token": "base64encodedandsignedstring",
"token_type": "bearer",
"expires_in": 299,
"refresh_token": "f87ae3bee04b4ca39af6f22a198274df",
"as:client_id": "mysite",
"userName": "me#email.com",
".issued": "Wed, 19 Apr 2017 20:15:58 GMT",
".expires": "Wed, 19 Apr 2017 20:20:58 GMT"
}
And another call that takes the refresh_token and uses it to generate a new access token. From the Api standpoint this should enable me to pass in the refresh_token and generate a new JWT with a new expires date.
I'm not 100% sure on how to wire up the Angular side to support this, my login function:
var _login = function (LoginData) {
var data = "grant_type=password&username=" + LoginData.UserName + "&password=" + LoginData.Password + "&client_id=4TierWeb";
var deferred = $q.defer();
$http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {
localStorageService.set('authorizationData', { token: response.data.access_token, userName: LoginData.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
_authentication.isAuth = true;
_authentication.userName = LoginData.UserName;
deferred.resolve(response);
}, function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
My refresh function:
var _refreshToken = function () {
var deferred = $q.defer();
var authData = localStorageService.get('authorizationData');
if (authData) {
if (authData.useRefreshTokens) {
var data = "grant_type=refresh_token&refresh_token=" + authData.refreshToken + "&client_id=4TierWeb";
localStorageService.remove('authorizationData');
$http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {
localStorageService.set('authorizationData', { token: response.data.access_token, userName: response.data.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
// response.headers.Authorization = 'Bearer ' + response.token;
deferred.resolve(response);
}, function (err, status) {
_logOut();
deferred.reject(err);
});
}
}
return deferred.promise;
};
And my interceptor:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {
request: function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function (error) {
if (error.status === 401) {
$location.path('/login');
}
return $q.reject(error);
}
};
return authInterceptorServiceFactory;
}]);
My interceptor works great without the refresh mechanism in place as above, but when I add the refresh mechanism:
authService.RefreshToken();
config.headers.Authorization = 'Bearer ' + authData.token;
I'm able to pull down a new JWT but the next line doesn't seem to be working correctly anymore, I'm getting 401 on my landing page and there is no bearer token in the payload, what am I missing here?
Updated Interceptor:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
if (rejection.status === 401) {
var authService = $injector.get('authService');
// refresh the token
authService.refreshToken().then(function() {
// retry the request
var $http = $injector.get('$http');
return $http(rejection.config);
});
}
return $q.reject(rejection);
}
};
}
]);
You need to wait for the refresh_token request to complete obtaining a new access token and then use the response to issue a new request.
Like: authService.refreshToken().then(doRequest())
Lets suppose that you have 2 functions inside authService:
function getAccessToken() { ...get access token like in login()... } - returning Promise
function refreshToken() { ...existing logic... } - returning Promise
Let us say that you will use jwt_decode(jwt) to decode the JWT token.
I think you can go two ways with your implementation:
1st way: get the token and immediately subscribe in order to refresh when expired
function getAccessToken() {
...
return $http(...)
.then(function(response) {
// ...correct credentials logic...
if(authService.refreshTimeout) {
$window.clearTimeout(authService.refreshTimeout);
}
// decode JWT token
const access_token_jwt_data = jwt_decode(response.data.access_token);
// myOffset is an offset you choose so you can refresh the token before expiry
const expirationDate = new Date(access_token_jwt_data * 1000 - myOffset);
// refresh the token when expired
authService.refreshTimeout = $window.setTimeout(function() {
authService.refreshToken();
});
return response.data;
})
.catch(function(error) {
// ...invalid credentials logic...
return $q.reject(error);
});
}
NOTE: You can use window instead of $window. I don't think that you actually need a new digest cycle at that moment. A new digest will be launched when $http request completes successfully or not.
NOTE: This means that you need to take care also of the case when you reload the page. Thus re-enabling the refresh timeout. So you can reuse the logic within getAccessToken() for subscribing to expiry date but this time you get the token from the localStorage. This means that you can refactor this logic into a new function called something like function subscribeToTokenExpiry(accessToken). So you can call this function in your authService constructor if there is an access token in your localStorage.
2nd way: refresh the token in your HTTP interceptor after receiving an error code from server.
You can refresh your token if your interceptor receives an error that match a token expiry case. This depends strongly on your back-end implementation so you may receive HTTP 401 or 400 or anything else and some custom error message or code. So you need to check with your back-end. Also check if they are consistent in returning the HTTP statuses and error codes. Some implementation details might change over time and framework developers might advice users to not rely on that specific implementation because is only for internal use. In that case you can leave only the HTTP status and omit the code, as you will have better chances of having the same in the future. But ask your back-end or the ones that created the framework.
NOTE: regarding Spring OAuth2 back-end implementation, find the details at the end of this answer.
Getting back to your code, your interceptor should look like:
app.factory('authInterceptorService',
['$q', '$location', 'localStorageService', 'authService', '$injector',
function ($q, $location, localStorageService, authService, $injector) {
var authInterceptorServiceFactory = {
request: function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function (response) {
let promise = $q.reject(response);
if (response.status === 401
&& response.data
&& response.data.error === 'invalid_token') {
// refresh the token
promise = authService.refreshToken().then(function () {
// retry the request
const $http = $injector.get('$http');
return $http(response.config);
});
}
return promise.catch(function () {
$location.path('/login');
return $q.reject(response);
});
}
};
return authInterceptorServiceFactory;
}]);
Spring Security OAuth2 back-end related:
I add this section for those curious about Spring Authorization Server implementation as Spring is a very popular framework in the Java world.
1) Expiry date
Regarding the expiry date, this is expressed in seconds. You will find the "exp" key inside your access_token and refresh_token after you JWT decode the string.
This is in seconds because you add the JwtAccessTokenConverter which uses DefaultAccessTokenConverter that does:
if (token.getExpiration() != null) {
response.put(EXP, token.getExpiration().getTime() / 1000);
}
JwtAccessTokenConverter is added when the Authorization Server is being configured:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// ...
endpoints.accessTokenConverter(jwtAccessTokenConverter)
// ...
}
}
2) Access token expired response
You might need to handle one or both of HTTP 400 and HTTP 401 statuses and rely on { "error": "invalid_token" }. But this depends strongly on how the back-end was implemented using Spring.
See the explanations bellow:
Regarding, the resource server configuration (the one to which we send the request to in order to get the resource we want), the flow is as follows:
OAuth2AuthenticationProcessingFilter servlet filter to get access token from request
OAuth2AuthenticationManager to parse token string
DefaultTokenServices to obtain the access token object.
OAuth2AuthenticationProcessingFilter try catch will delegate the exceptions to OAuth2AuthenticationEntryPoint which creates the response for the exception.
DefaultTokenServices is a ResourceServerTokenServices implementation.
There are two possible such implementations, one is this DefaultTokenServices and the other is RemoteTokenServices.
If we use DefaultTokenServices then the token will be checked on the resource server. This means that the resource server has knowledge of the key that signed the token in order to check the token validity. This approach means distributing the key to all parties that want such behavior.
If we use RemoteTokenServices then the token will be checked against /oauth/check_token endpoint handled by CheckTokenEndpoint.
On expiry CheckTokenEndpoint will create an InvalidTokenException with HTTP 400, that will converted by OAuth2ExceptionJackson2Serializer into HTTP 400 with data { "error": "invalid_token", "error_description": "Token has expired" }.
On the other hand DefaultTokenServices will create also a InvalidTokenException exception but with other message and without overriding the HTTP status thus being HTTP 401 in the end. So this will become HTTP 401 with data { "error": "invalid_token", "error_description": "Access token expired: myTokenValue" }.
Again this, HTTP 400 or HTTP 401, happens because InvalidTokenException is thrown in both cases DefaultTokenServices throws without overriding getHttpErrorCode() which is 401 but CheckTokenEndpoint overrides it with 400.
Note: I added a Github Issue in order to check if this behavior, 400 vs 401, is correct.
I've used this interceptor at a couple of occasions without any problems.
You can set it up to refresh the token silently and only throw an error (and navigate to the login screen) if the refresh fails. Hope this helps
Is it secure to use a refresh token in an Angular application ? I'am not sure...
The OIDC implicit flow (which is the flow used for SPA or mobile apps), there is no refresh token involved.
I am using the Django REST token authentication for my API.
I posted my credentials to obtain token endpoint. However when I try to set the header in a correct way it keeps responding with a http 401 error. I tried it using curl -X GET http://127.0.0.1:8000/events/ -H 'Authorization: Token 4d92d36768ca5d555b59cf68899eceab39c23704 ' and that does work! This is my code:
app.controller('HomeController', ['$scope','$http', function($scope,$http) {
$scope.username = '';
$scope.password = '';
$scope.submitLogin = function () {
var credentials = {
username : $scope.username,
password : $scope.password,
};
var req = $http.post('http://127.0.0.1:8000/api-token-auth/', credentials);
req.success(function(data, status, headers, config) {
$scope.token = data.token;
var str1 = 'Token ';
$scope.tokenheader = str1.concat($scope.token);
$http.defaults.headers.common.Authorization = $scope.tokenheader;
});
req.error(function(data, status, headers, config) {
alert( "failure message: " + JSON.stringify({data: data}));
});
};
$scope.getEvents = function () {
var req = {
method: 'GET',
url: 'http://127.0.0.1:8000/events/',
}
$http(req).then(
function() {
console.log('succes')
},
function(){
console.log('fail')
});
};
}]);
And the error message in chrome dev tools:
XMLHttpRequest cannot load http://127.0.0.1:8000/events/.
Response for preflight has invalid HTTP status code 401
How do I get rid of this 401 error?
Edit: I just found out the fault lies in the fact that I did not have CORS installed on my API. I was using a CORS plugin in chrome that worked for the authentication part of my api but not for my events url!
Did you check that the token is actually added to your request?
You can do this for example using the Chrome developers tools.
Personally I prefer to use the $httpprovider.interceptor as described in:
angularjs $httpProvider interceptor documentation
This ensures that the tokens are always present on any call.
If you are accessing more than one API, you should consider adding something like:
$httpProvider.interceptors.push(['$q', '$location', '$log', 'loginService', 'restHelperService',
function ($q, $location, $log, loginService, restHelperService) {
return {
request: function (config) {
// check if the request comes with an url
if (config.url) {
// check that the call is to the REST api, if yes add token
if (restHelperService.isRestCall(config.url)) {
// add auth header or revert to login
if (loginService.userIsLoggedIn()) {
config.headers = config.headers || {};
config.headers.Authorization = 'Token ' + loginService.getToken().token;
} else {
$location.path('/login');
}
}
}
return config;
},
responseError: function (response) {
if (response.status === 401 || response.status === 403) {
// clear auth token if the REST call failed with the current token
if (response.config && response.config.url && restHelperService.isRestCall(response.config.url)) {
$log.debug(" restCall failed due to bad credentials, resetting credentials");
loginService.resetCredentials();
$location.path('/login');
}
}
return $q.reject(response);
}
};
}]);
}])
This avoid issues that will arise when you start adding the token to API calls that don't expect them. Also the code ensures that a user will be automatically redirected to the login page if the credentials are not valid.
The example, I'm using two additional services. A loginService that manages the tokens and a restHelperService that manages the urls of the REST framework.
I would recommend doing the same as else it will be hard to access the credentials from outside your controller.
You need to add Token to the headers:
get($http, "/some_url", {headers: {"Authorization": "Token " + $your_token}}
....
....
);
Response code 401 means Unauthorized. If you are using Token based authentication then in case of fail it would be 403, Forbidden.
So my guess would be that it's username/password who is messing with it. In your curl example you are not using them.
Hi am using basic authentication for webservie integration.
$http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode(Username + ':' + password);
$http({method: 'GET', url: 'http:url.com'}).
success(function(data) {
if(data=='' || data==null || data=='undefined'){
var alertPopup = $ionicPopup.alert({
title: 'Info!',
template: 'Invalid Password, or no user found with this Email Address'
});
alertPopup.then(function(res) {
console.log('Invalid Password, or no user found with this Email Address ');
});
}
This code is working fine for me .but my problem is if one user is logedin using username and password.then logged out after that another user try to logged in with different username and password will get the previous user loged in. how to clear the header authentication data?
You could probably redefine the header with
$http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode(newUsername + ':' + newPassword)
It seems that,
You have not replaced the Authorization header when
the user logged out
logged in as a different user
Login - call this every time a user logs in
function setCredentials(username, password) {
$http.defaults.headers.common['Authorization'] = 'Basic ' + Base64.encode(username + ':' + password);
};
Logout - call this when a user logs out
function clearCredentials() {
$http.defaults.headers.common.Authorization = 'Basic ';
};
The above snippets simplified from this tutorial - AngularJS Basic HTTP Authentication Example. i recommend reading it
In addition,
Please note that using basic http authentication is insecure because the password passed on each request with no encryption!
As an alternative, you can change basic http authentication to a server side authentication using sessions (comment the server your'e using and i'll link you to example)
If you still decides to keep the basic http authentication, at least use HTTPS !
I've found this solution working:
When initiating a logout, first try making a bad request with a fake user to throw away the currently cached credentials.
I have this function doing the requests (it's using jquery's $.ajax with disabled asynch calls):
function authenticateUser(username, hash) {
var result = false;
var encoded = btoa(username + ':' + hash);
$.ajax({
type: "POST",
beforeSend: function (request) {
request.setRequestHeader("Authorization", 'Basic ' + encoded);
},
url: "user/current",
statusCode: {
401: function () {
result = false;
},
200: function (response) {
result = response;
}
},
async: false
});
return result;
}
So when I try to log a user out, this happens:
//This will send a request with a non-existant user.
//The purpose is to overwrite the cached data with something else
accountServices.authenticateUser('logout','logout');
//Since setting headers.common.Authorization = '' will still send some
//kind of auth data, I've redefined the headers.common object to get
//rid of the Authorization property
$http.defaults.headers.common = {Accept: "application/json, text/plain, */*"};
I want to implement the Sliding expiration concept with json web tokens using angular, nodejs and express-jwt. I'm a little confused on how to do this, and am struggling to find any example of refresh tokens or and other material relating to sessions with these technologies/frameworks.
A few options I was thinking of were
Generating a new token with each request after the initial login
Keeping track of issued token on the server side along
But I'm honestly not sure, please help
I managed to implement this scenario.
What I've done...
On the server:
-Enable an API endpoint for signin. This endpoint will respond with the Json Web Token in the header. The client side has to catch it (with $http interceptors) and save it (I use local storage). The client will also manage the refreshed tokens sent by the server.
-On every request to the server configure a middleware in express to validate the token. At first I tried express-jwt module but jsonwebtoken was the right one for me.
For specific routes you may want to disable the middleware. In this case signin and signout.
var jwtCheck = auth.verifyJWT;
jwtCheck.unless = unless;
app.use('/api', jwtCheck.unless({path: [
'/api/auth/signin',
'/api/auth/signout'
]}));
-The middleware verifyJWT always responds with a token in the header. If the token needs to be refreshed a refreshed function is called.
jwtLib is my own library where the code lives to create, refresh and fetch jwt tokens.
function(req, res, next) {
var newToken,
token = jwtLib.fetch(req.headers);
if(token) {
jwt.verify(token, config.jwt.secret, {
secret: config.jwt.secret
}, function(err, decoded) {
if(err) {
return res.status(401).send({
message: 'User token is not valid'
});
}
//Refresh: If the token needs to be refreshed gets the new refreshed token
newToken = jwtLib.refreshToken(decoded);
if(newToken) {
// Set the JWT refreshed token in http header
res.set('Authorization', 'Bearer ' + newToken);
next();
} else {
res.set('Authorization', 'Bearer ' + token);
next();
}
});
} else {
return res.status(401).send({
message: 'User token is not present'
});
}
};
-The refresh function (jwtLib). As argument needs a decoded token, see above that jsonwebtoken resolve a decoded when call to jwt.verify().
If you create during signin a token with an expiration of 4 hours and have a refresh expiration of 1 h (1 * 60 * 60 = 3600 secs) that means that the token will be refreshed if the user has been inactive for 3 hours or more, but not for more than 4 hours, because the verify process would fail in this case (1 hour window of refreshing). This avoids generating a new token on each request, only if the token will expire in this time window.
module.exports.refreshToken = function(decoded) {
var token_exp,
now,
newToken;
token_exp = decoded.exp;
now = moment().unix().valueOf();
if((token_exp - now) < config.jwt.TOKEN_REFRESH_EXPIRATION) {
newToken = this.createToken(decoded.user);
if(newToken) {
return newToken;
}
} else {
return null;
}
};
On the client (Angularjs):
-Enable a client side for login. This calls the server endpoint. I use Http Basic Authentication encoded with base64.
You can use base64 angular module to encode the email:password
Note that on success I do not store the token on the localStorage or Cookie. This will be managed by the http Interceptor.
//Base64 encode Basic Authorization (email:password)
$http.defaults.headers.common.Authorization = 'Basic ' + base64.encode(credentials.email + ':' + credentials.password);
return $http.post('/api/auth/signin', {skipAuthorization: true});
-Configure the http interceptors to send the token to the server on every request and store the token on the response. If a refreshed token is received this one must be stored.
// Config HTTP Interceptors
angular.module('auth').config(['$httpProvider',
function($httpProvider) {
// Set the httpProvider interceptor
$httpProvider.interceptors.push(['$q', '$location', 'localStorageService', 'jwtHelper', '$injector',
function($q, $location, localStorageService, jwtHelper, $injector) {
return {
request: function(config) {
var token = localStorageService.get('authToken');
config.headers = config.headers || {};
if (token && !jwtHelper.isTokenExpired(token)) {
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
requestError: function(rejection) {
return $q.reject(rejection);
},
response: function(response) {
//JWT Token: If the token is a valid JWT token, new or refreshed, save it in the localStorage
var Authentication = $injector.get('Authentication'),
storagedToken = localStorageService.get('authToken'),
receivedToken = response.headers('Authorization');
if(receivedToken) {
receivedToken = Authentication.fetchJwt(receivedToken);
}
if(receivedToken && !jwtHelper.isTokenExpired(receivedToken) && (storagedToken !== receivedToken)) {
//Save Auth token to local storage
localStorageService.set('authToken', receivedToken);
}
return response;
},
responseError: function(rejection) {
var Authentication = $injector.get('Authentication');
switch (rejection.status) {
case 401:
// Deauthenticate the global user
Authentication.signout();
break;
case 403:
// Add unauthorized behaviour
break;
}
return $q.reject(rejection);
}
};
}
]);
}
]);