I have set token session expiration time 15 minute on the AAD,
overviews
Login into the web application and getting a token with an expiration time of 15 minutes and storing it in local sessions storage.
After login calling the timmer method which is running every second. in this method getting token expiration time and set logic to call the acquireTokenSilent method and refresh token silently.
version
MSAL : 0.2.4,
Angular : 6
code
setTimer() {
this.interval = setInterval(() => {
const token = sessionStorage.getItem('msal.idtoken');
if (token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
const sessionPayload = JSON.parse(jsonPayload);
const date = new Date(0);
const sessionTimeOut = date.setUTCSeconds(sessionPayload.exp - 2 * 60);
const currentTime = new Date();
if (currentTime.valueOf() > sessionTimeOut.valueOf()) {
this.stopInterval();
this.getToken();
}
}
}, 1000);
}
getToken() {
return this.app.acquireTokenSilent(this.applicationConfig.b2cScopes).then(
(accessToken) => {
this.accessToken = accessToken;
this.saveAccessTokenToCache(accessToken);
return accessToken;
},
(error) => {
return this.app
.acquireTokenPopup(this.applicationConfig.b2cScopes)
.then(
(accessToken) => {
this.accessToken = accessToken;
this.saveAccessTokenToCache(accessToken);
return accessToken;
},
(err) => {
console.error('error', error);
}
);
}
);
}
stopInterval() {
clearInterval(this.interval);
}
saveAccessTokenToCache(accessToken: string): void {
sessionStorage.setItem('msal.idtoken', accessToken);
this.setTimer();
}
Issue
after login in the 13-minute token is refresh but the second time refresh token call is not happening.
requirement
Need refresh token every 2 minutes before the token expiration time.
Please Help me, friends.
Thanks!
Related
Good day all, Please I have this code below for testing download speed, and has been working for some time now. But recently always bring this error and doesn't work any more. Please help look into it.
Thanks
Tried with this url: 'https://drive.google.com/open?id=1MBHJXeRxMLLwHFpqbgTdEPsFArMM0cz7'
[Error: Unable to resolve host "drive.google.com": No address associated with hostname]
Tried with this url: 'https://upload.wikimedia.org/wikipedia/commons/b/b9/Pizigani_1367_Chart_1MB.jpg'
[Error: Unable to resolve host "upload.wikimedia.org": No address associated with hostname]
const downloadSizeInBits = 12000000;
const metric = 'MBps';
export const measureConnectionSpeed = (imageURIParam: string): any => {
const imageURI = imageURIParam
? imageURIParam
: 'https://drive.google.com/open?id=1MBHJXeRxMLLwHFpqbgTdEPsFArMM0cz7';
return new Promise((resolve, reject) => {
const startTime = new Date().getTime();
RNFetchBlob.config({
fileCache: false,
})
.fetch('GET', imageURI, {})
.then((res) => {
const endTime = new Date().getTime();
const duration = (endTime - startTime) / 1000;
const speed = downloadSizeInBits / (1024 * 1024 * duration);
resolve({metric, speed});
})
.catch(reject);
});
};
//Called from here in another component
getDownloadSpeed = async () => {
try {
const networkSpeed = await measureConnectionSpeed();
this.setState({value: networkSpeed.speed});
return networkSpeed.speed;
} catch (error) {
//handle error
ToastAndroid.show(
'Network Issues, unable to get download speed',
ToastAndroid.LONG,
);
console.log(error);
}
};
Thanks
Tim
How can I simulate that the auth token in local storage is expired?
This is the jwt:
export function parseJwt(token) {
const base64Url = token.split('.')[0];
return JSON.parse(window.atob(base64Url));
}
export function isTokenExpired(token) {
const t = parseJwt(token);
if (t.exp && Date.now() < t.exp * 1000) {
return false;
}
return true;
}
And this is localstorage:
export const isItemExpired = value => {
if (value.expiration && Date.now() < value.expiration) {
return false;
}
return true;
};
export const getItemExpiration = () => {
const d = new Date();
d.setDate(d.getDate());
return d.getTime();
};
export const setItem = (key, data, expiration) => {
const localStorageState = data;
if (localStorageState) {
localStorageState.expiration = expiration || getItemExpiration();
}
window.localStorage.setItem(key, JSON.stringify(localStorageState));
};
export const removeItem = key => {
window.localStorage.removeItem(key);
};
export const getItem = key => {
let value = null;
try {
value = JSON.parse(window.localStorage.getItem(key));
} catch (err) {
return null;
}
return value;
};
My app throws an error when the session refresh token is expired. I am new to tokens and auth in react. How can I set the expiration to be in 5 seconds after being created so that I can find out what the issue is?
With the google oauth2 library, I can successfully authenticate a user on their first pass through, get their refresh token and first access token. Until the token expires, everything works as expected.
However, when the access token expires, I need to get a new access token and store these tokens in my data store using the existing refresh token. I am aware the documentation states tokens should re-fetch themselves when they expire, but as I am creating a new client for each call (to ensure tokens are not re-used between users), I think the client gets torn down before a token gets chance to refresh itself.
Inspecting what the library does calling the actual google api, I should be able to get new access tokens by calling the client.refreshAccessToken() method, the response from this call gives me the invalid_grant Bad Request error. I have compared the actual api request this method makes to the one on google oauth2 playground and the two calls are identical - although their call for refreshing their token works and mine does not.
Attached is my code as it now currently stands Please send help - I don't have any hair left to pull out!
const { google } = require('googleapis')
const scopes = [
'https://www.googleapis.com/auth/spreadsheets.readonly',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/drive.readonly'
]
module.exports = (env, mongo) => {
const getBaseClient = () => {
const { OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL } = env.credentials
return new google.auth.OAuth2(
OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL
)
}
const getNewAccessTokens = async (authId, refreshToken) => {
const { tokens } = await getBaseClient().getToken(refreshToken)
await mongo.setAccessTokensForAuthUser(authId, { ...tokens, refresh_token: refreshToken })
return tokens
}
const getAuthedClient = async (authId) => {
let tokens = await mongo.getAccessTokensForAuthUser(authId)
if (!tokens.access_token) {
tokens = await getNewAccessTokens(authId, tokens.refresh_token)
}
const client = getBaseClient()
client.setCredentials(tokens)
if (client.isTokenExpiring()) {
const { credentials } = await client.refreshAccessToken()
tokens = { ...credentials, refresh_token: tokens.refreshToken }
await mongo.setAccessTokensForAuthUser(authId, tokens)
client.setCredentials(tokens)
}
return client
}
const generateAuthUrl = (userId) => {
return getBaseClient().generateAuthUrl({
access_type: 'offline',
scope: scopes,
state: `userId=${userId}`
})
}
const getUserInfo = async (authId) => {
const auth = await getAuthedClient(authId)
return google.oauth2({ version: 'v2', auth }).userinfo.get({})
}
const listSheets = async (authId) => {
const auth = await getAuthedClient(authId)
let nextPageToken = null
let results = []
do {
const { data } = await google
.drive({ version: 'v3', auth })
.files.list({
q: 'mimeType = \'application/vnd.google-apps.spreadsheet\'',
includeItemsFromAllDrives: true,
supportsAllDrives: true,
corpora: 'user',
orderBy: 'name',
pageToken: nextPageToken
})
nextPageToken = data.nextPageToken
results = results.concat(data.files)
} while (nextPageToken)
return results
}
return {
generateAuthUrl,
getUserInfo,
listSheets
}
}
I solved my own problem.
I was conflating access_codes with refresh_tokens, and believed the code you receive from the auth url was the refresh_token, storing it, and attempting to reuse it to get more access_tokens. This is wrong. Don't do this.
You get the access_code from the authentication url, and the first time you use that with the client.getToken(code) method, you receive the refresh_token and access_token.
I've attached updated and working code should anyone wish to use it.
I should also mention that I added prompt: 'consent' to the auth url so that you always receive an access_code you can use to get a refresh_token when someone re-authenticates (as if you don't, then a call to client.getToken() does not return a refresh_token (part of what was confusing me in the first place).
const { google } = require('googleapis')
const scopes = [
'https://www.googleapis.com/auth/spreadsheets.readonly',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/drive.readonly'
]
module.exports = (env, mongo) => {
const getBaseClient = () => {
const { OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL } = env.credentials
return new google.auth.OAuth2(
OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CALLBACK_URL
)
}
const getAuthedClient = async (authId) => {
let tokens = await mongo.getAccessTokensForAuthUser(authId)
const client = getBaseClient()
client.setCredentials(tokens)
if (client.isTokenExpiring()) {
const { credentials } = await client.refreshAccessToken()
tokens = { ...credentials, refresh_token: tokens.refresh_token }
await mongo.setAccessTokensForAuthUser(authId, tokens)
client.setCredentials(tokens)
}
return client
}
const generateAuthUrl = (userId) => {
return getBaseClient().generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: scopes,
state: `userId=${userId}`
})
}
const getUserInfo = async (accessCode) => {
const auth = getBaseClient()
const { tokens } = await auth.getToken(accessCode)
auth.setCredentials(tokens)
const { data } = await google.oauth2({ version: 'v2', auth }).userinfo.get({})
return { ...data, tokens }
}
const listSheets = async (authId) => {
const auth = await getAuthedClient(authId)
let nextPageToken = null
let results = []
do {
const { data } = await google
.drive({ version: 'v3', auth })
.files.list({
q: 'mimeType = \'application/vnd.google-apps.spreadsheet\'',
includeItemsFromAllDrives: true,
supportsAllDrives: true,
corpora: 'user',
orderBy: 'name',
pageToken: nextPageToken
})
nextPageToken = data.nextPageToken
results = results.concat(data.files)
} while (nextPageToken)
return results
}
return {
generateAuthUrl,
getUserInfo,
listSheets
}
}
After logging in the scheduleRefresh() function is called and it will continuously refresh the token. The problem occurs when I refresh the page, or when an authenticated user tries accessing the web page again. startupTokenRefresh is called on startup, the token is refreshed, but the token is never scheduled to be refreshed again like its suppose to. If I refresh the page after I know the token has expired I get a token_not_provided error but I can see in the network console the token is being refreshed a couple of seconds after this error or the page has loaded. If I refresh again the request to my api is made fine but again the token does not refresh after its expired.
What am I doing wrong?
app.component.ts (on app start up)
platform.ready().then(() => {
storage.ready().then(() => storage.get('token'))
.then(token => {
storage.set('token', token);
authService.token = token;
authService.authNotifier.next(true);
authService.checkToken();
authService.startupTokenRefresh();
});
authService.authenticationNotifier().subscribe((authed) => {
if (authed) {
this.rootPage = TabsPage;
} else {
authService.logout();
this.rootPage = LoginPage;
}
});
}
auth.service.ts
jwtHelper: JwtHelper = new JwtHelper();
token;
refreshSubscription: any;
authNotifier: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
authenticationNotifier(): Observable<boolean> {
return this.authNotifier;
}
refresh(): Observable<any> {
console.log("in refresh()");
let URL = `${myApi}/refresh?token=${this.token}`;
return this.authHttp.get(URL)
.map((rsp) => {
this.token = rsp.json().token;
this.storage.ready().then(() => this.storage.set('token', this.token));
this.authNotifier.next(true);
return rsp.json().token;
},
err => {
this.authNotifier.next(false);
this.logout();
console.log(err);
})
.share();
}
checkToken() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
}
public scheduleRefresh() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
else {
// If the user is authenticated, use the token stream provided by angular2-jwt and flatMap the token
let source = this.authHttp.tokenStream.flatMap(
token => {
let jwtIat = this.jwtHelper.decodeToken(this.token).iat;
let jwtExp = this.jwtHelper.decodeToken(this.token).exp;
let iat = new Date(0);
let exp = new Date(0);
let delay = (exp.setUTCSeconds(jwtExp) - iat.setUTCSeconds(jwtIat));
return Observable.interval(delay);
});
this.refreshSubscription = source.subscribe(() => {
this.refresh().subscribe((res) => console.log('-> Refreshed...'),
(error) => console.log('Refresh error: ' + JSON.stringify(error)))
});
}
}
public startupTokenRefresh() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
else {
// Get the expiry time to generate a delay in milliseconds
let now: number = new Date().valueOf() / 1000;
let jwtExp: number = this.jwtHelper.decodeToken(this.token).exp;
let iat: number = this.jwtHelper.decodeToken(this.token).iat;
let refreshTokenThreshold = 10; //seconds
let delay: number = jwtExp - now;
let totalLife: number = (jwtExp - iat);
(delay < refreshTokenThreshold ) ? delay = 1 : delay = delay - refreshTokenThreshold;
// Use the delay in a timer to // run the refresh at the proper time
return Observable.timer(delay * 1000);
});
// Once the delay time from above is reached, get a new JWT and schedule additional refreshes
source.subscribe(() => {
this.refresh().subscribe(
(res) => {
console.log('-> Refreshed on startup');
this.scheduleRefresh();
},
(error) => console.log('-> Refresh error:' + JSON.stringify(error)))
});
}
}
public unscheduleRefresh() {
console.log("unsched");
if (this.refreshSubscription) {
this.refreshSubscription.unsubscribe();
}
}
login.ts
onLogin() {
this.authService.login(this.loginForm.value.username, this.loginForm.value.password)
.subscribe(
(response) => {
this.storage.ready().then(() => this.storage.set('token', response.token));
this.authService.token = response.token;
this.authService.authNotifier.next(true);
},
error => {
console.log(error);
this.loginError = true;
this.authService.authNotifier.next(false);
},
() => {
console.log("login success");
this.authService.scheduleRefresh();
this.navCtrl.push(TabsPage);
},
);
}
}
I'm currently trying to connect to the CEX.IO bitcoin exchange's websocket. Websocket connection is OK but at the time of authentication, I have the error: Timestamp is not in 20sec range. I don't know what this error.
Test case 1 & 2 for createSignature is OK (https://cex.io/websocket-api#authentication).
Code for calculating the signature and request params
const WebSocket = require('ws');
const cexioWs = new WebSocket(
'wss://ws.cex.io/ws/',
{
perMessageDeflate: false
}
);
function createAuthRequest(apiKey, apiSecret) {
let curTime = Math.floor(Date.now() / 1000);
let hmac = crypto.createHmac('sha256', apiSecret);
hmac.update(curTime.toString());
hmac.update(apiKey);
let args =
{
e: "auth",
auth: {
key: apiKey,
signature: hmac.digest('hex'), //createSignature(curTime, apiKey, apiSecret),
timestamp: curTime
}
};
let authMessage = JSON.stringify(args);
console.log(args);
return authMessage;
}
cexioWs.on('message', (mess, error) => {
//console.log("connected");
console.log("cexio message");
console.log(mess);
let JSONMess = JSON.parse(mess);
if (JSONMess.e === "connected") {
cexioWs.send(createAuthRequest(key, secret));
cexioWs.send(JSON.stringify({
e: "subscribe",
roomss: [
"tickers"
]
}));
}
if (JSONMess.e === "ping") {
console.log("pong message");
cexioWs.send(JSON.stringify({e: "pong"}));
}
});
Here is working code:
const crypto = require('crypto')
const WebSocket = require('ws')
var apiKey = ''
var apiSecret = ''
const cexioWs = new WebSocket('wss://ws.cex.io/ws/', {perMessageDeflate: false });
function createSignature(timestamp, apiKey, apiSecret){
var hmac = crypto.createHmac('sha256', apiSecret );
hmac.update( timestamp + apiKey );
return hmac.digest('hex');
}
function createAuthRequest(apiKey, apiSecret ){
var timestamp = Math.floor(Date.now() / 1000);
var args = { e: 'auth', auth: { key: apiKey,
signature: createSignature(timestamp, apiKey, apiSecret), timestamp: timestamp } };
var authMessage = JSON.stringify( args );
return authMessage;
}
cexioWs.on('message', (mess, error) => {
console.log("cexio message");
console.log(mess);
let JSONMess = JSON.parse(mess);
if (JSONMess.e === "connected") {
cexioWs.send(createAuthRequest(apiKey, apiSecret));
cexioWs.send(JSON.stringify({
e: "subscribe",
rooms: [
"tickers"
]
}));
}
if (JSONMess.e === "ping") {
console.log("pong message");
cexioWs.send(JSON.stringify({e: "pong"}));
}
});
Don't know if this helps but I had the same problem for two days, checked everything then I checked and the code looked absolutely fine. Later on I checked what the actual time I was getting and compared it to the Internet time. My computer's time was 4 minutes ahead of Internet time and my settings were off for 'update time from Internet'.
After sync'ing my computer's time with Internet I ran the script and it worked perfectly.
Moral of the story, make sure your PC's time and the Internet's time are the same.
Goodluck!