A decaffeinated de-angularized drop-in replacement for ng-token-auth.
npm i vanilla-token-auth
Supports most of the ng-token-auth config, plus a few additional configs for cookie transport (added in this devise_token_auth
PR) and wiring into an existing framework's features.
If you want to use cookie transport with devise_token_auth
for the token (instead of using headers/localStorage), specify transport = 'cookies'
and storage = undefined
in the config.
Make use of the httpWrapper
config, which should be a fetch-like wrapper around your networking library. See examples below.
If you still need to support AngularJS with this library, then in addition to httpWrapper
, you can make use of the broadcast
and navigate
config callbacks to wrap $broadcast
and routing.
If you still need to use the header interceptor logic from ng-token-auth
, see this sample AngularJS module that wraps an instance of DeviseTokenAuthClient
and uses this ng-token-auth logic:
angular
.module('DeviseTokenAuthClient', [])
.factory('ngDeviseTokenAuthClient', [
'$injector',
$injector => {
return new DeviseTokenAuthClient({
// config
});
},
])
.config([
'$httpProvider',
$httpProvider => {
// responses are sometimes returned out of order. check that response is
// current before saving the auth data.
function tokenIsCurrent(ngDeviseTokenAuthClient, headers) {
const oldTokenExpiry = Number(ngDeviseTokenAuthClient.getExpiry());
const newTokenExpiry = Number(ngDeviseTokenAuthClient.getConfig().parseExpiry(headers || {}));
return newTokenExpiry >= oldTokenExpiry;
}
// uniform handling of response headers for success or error conditions
function updateHeadersFromResponse(ngDeviseTokenAuthClient, resp) {
const newHeaders = {};
const tokenFormat = ngDeviseTokenAuthClient.getConfig().tokenFormat;
Object.keys(tokenFormat).forEach(key => {
if (resp.headers(key)) {
newHeaders[key] = resp.headers(key);
}
});
if (tokenIsCurrent(ngDeviseTokenAuthClient, newHeaders)) {
ngDeviseTokenAuthClient.setAuthHeaders(newHeaders);
}
}
// this is ugly...
// we need to configure an interceptor (must be done in the configuration
// phase), but we need access to the $http service, which is only available
// during the run phase. the following technique was taken from this
// stackoverflow post:
// http://stackoverflow.com/questions/14681654/i-need-two-instances-of-angularjs-http-service-or-what
$httpProvider.interceptors.push([
'$injector',
$injector => ({
request(req) {
$injector.invoke([
'$http',
'ngDeviseTokenAuthClient',
// eslint-disable-next-line consistent-return
function onRequest($http, ngDeviseTokenAuthClient) {
if (req.url.match(ngDeviseTokenAuthClient.apiUrl())) {
return (() => {
const result = [];
const object = ngDeviseTokenAuthClient.retrieveData('auth_headers') || {};
Object.keys(object).forEach(key => {
result.push((req.headers[key] = object[key]));
});
return result;
})();
}
},
]);
return req;
},
response(resp) {
$injector.invoke([
'$http',
'ngDeviseTokenAuthClient',
// eslint-disable-next-line consistent-return
function onResponse($http, ngDeviseTokenAuthClient) {
if (resp.config.url.match(ngDeviseTokenAuthClient.apiUrl())) {
return updateHeadersFromResponse(ngDeviseTokenAuthClient, resp);
}
},
]);
return resp;
},
responseError(resp) {
$injector.invoke([
'$http',
'ngDeviseTokenAuthClient',
// eslint-disable-next-line consistent-return
function onResponseError($http, ngDeviseTokenAuthClient) {
if (
resp &&
resp.config &&
resp.config.url &&
resp.config.url.match(ngDeviseTokenAuthClient.apiUrl())
) {
return updateHeadersFromResponse(ngDeviseTokenAuthClient, resp);
}
},
]);
return $injector.get('$q').reject(resp);
},
}),
]);
// define http methods that may need to carry auth headers
const httpMethods = ['get', 'post', 'put', 'patch', 'delete'];
// disable IE ajax request caching for each of the necessary http methods
httpMethods.forEach(method => {
if ($httpProvider.defaults.headers[method] == null) {
$httpProvider.defaults.headers[method] = {};
}
$httpProvider.defaults.headers[method]['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
});
},
])
.run(['ngDeviseTokenAuthClient', ngDeviseTokenAuthClient => ngDeviseTokenAuthClient.initialize()]);
- AngularJS
$http
angularModule.factory('ngHttpWrapper', [
'$injector',
$injector => {
const $http = $injector.get('$http');
// Mimics fetch signature and return value (a native promise), see https://github.com/microsoft/TypeScript/blob/v4.6.3/lib/lib.dom.d.ts#L16450
/**
* @param {RequestInfo} input
* @param {RequestInit} [init]
* @returns {Promise<Response>}
*/
return (input, init = {}) => {
if (typeof input !== 'string') {
throw new Error('Must pass RequestInfo as a string');
}
const method = init.method ? init.method.toUpperCase() : 'GET';
const body = init.body && JSON.parse(init.body);
let qPromise;
switch (method) {
case 'GET':
qPromise = $http.get(input);
break;
case 'POST':
qPromise = $http.post(input, body);
break;
case 'PUT':
qPromise = $http.put(input, body);
break;
case 'DELETE':
qPromise = $http.delete(input);
break;
case 'PATCH':
throw new Error('Not implemented');
default:
throw new Error(`Request method "${method}" is not supported`);
}
return new Promise((resolve, reject) => {
qPromise
.then(response => {
resolve(
new Response(JSON.stringify(response.data), {
status: response.status,
statusText: response.statusText,
}),
);
})
.catch(error => {
reject(error);
});
});
};
},
]);
- RTK Query
// Mimics fetch signature and return value (a native promise), see https://github.com/microsoft/TypeScript/blob/v4.6.3/lib/lib.dom.d.ts#L16450
export const rtkQueryHttpWrapper = async (input: RequestInfo, init: RequestInit = {}): Promise<Response> => {
const response = await storeProvider.store!.dispatch(
createApiObject.endpoints.dynamicEndpoint.initiate({ input, init }),
);
if (response.status === 'rejected') {
throw response.error;
}
return new Response(JSON.stringify(response.data));
};
export default rtkQueryHttpWrapper;