I am working on react front end app and middleware is written in nodejs. I am using oauth access token which expires in 3600 ms so i need to create session for 7 days so user wont be logout before 7 days.
What is the way to create session ? Do I need to do in react app or node js app ?
PS We do not wat to implement refresh token approach. Any way to setup session and valid for 7 days ?
You can use an access token + refresh token to achieve this.
Use shorter access tokens and keep 7d expiry for the refresh token.
Upon the expiry of the access token, you can refresh it by passing the refresh token. This will work till your refresh token expiry is 7 days. Then the user has to log in again.
Something like the following.
export const generateAccessToken = (user: UserResponse): string => {
return jwt.sign(
{
userId: user.user_id,
clientId: user.client_id,
createdAt: new Date().getTime(),
storageKey: user.storageKey ?? "",
pipelineKey: user.pipelineKey ?? "",
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: `3600s` }
);
};
export const generateRefreshToken = async (
user: UserResponse
): Promise<string> => {
const refreshTOken = jwt.sign(
{
userId: user.user_id,
},
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: `7d` }
);
const status = await storeRefreshToken(user.user_id, refreshTOken);
if (status) {
return refreshTOken;
} else {
throw new Error("Error while storing refresh token");
}
};
refresh-token endpoint would be something like this
authRouter.post("/refresh-token", async (request: any, response: any) => {
await transformAndValidate(RefreshTokenRequestDto, request.body)
.then(async (refreshTokenRequest: any) => {
if (authenticateRefreshToken(refreshTokenRequest.refreshToken)) {
const dbRefreshToken = await getRefreshTokenByToken(
refreshTokenRequest.refreshToken
);
if (
dbRefreshToken &&
dbRefreshToken.user_id &&
dbRefreshToken.active &&
dbRefreshToken.expiry_at >= new Date()
) {
const user = await getUserById(dbRefreshToken.user_id);
if (user) {
const jwtToken = generateAccessToken(user);
response.status(200).send(
generateSuccessResponse({
accessToken: jwtToken,
refreshToken: dbRefreshToken.token,
fullName: user.username,
})
);
} else {
return response
.status(400)
.json(
generateFailedResponse(
"Invalid User",
AppCodes.REFRESHTOKENFAIL
)
);
}
} else {
return response
.status(400)
.json(
generateFailedResponse(
"Refresh Token Failed",
AppCodes.REFRESHTOKENFAIL
)
);
}
} else {
return response
.status(400)
.json(
generateFailedResponse(
"Refresh Token JWT Validation Failed",
AppCodes.REFRESHTOKENFAIL
)
);
}
})
.catch((err) => {
response
.status(400)
.json(
generateFailedResponse(
formatValidationErrorMsg(err),
AppCodes.VALIDATIONFAILED
)
);
});
});
Related
I want to add custom expires time for fitbit implicit auth flow the default expires time is a day you can customize it I want to make it for a year. If you are using the web version you can change it by directly changing the expires_in params in the url.
As shown in this below url.
https://www.fitbit.com/oauth2/authorize?response_type=token&client_id=randomid&redirect_uri=https%3A%2F%2Fauth.expo.io%2F%40albert%2Fyourapp&scope=activity%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight%20oxygen_saturation%20respiratory_rate%20temperature&expires_in=31536000
WebBrowser.maybeCompleteAuthSession();
const useProxy = Platform.select({ web: false, default: true });
// Endpoint
const discovery = {
authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize',
tokenEndpoint: 'https://api.fitbit.com/oauth2/token',
revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke',
};
const [request, response, promptAsync] = useAuthRequest(
{
responseType: ResponseType.Token,
clientId: 'randomid',
scopes: ['activity', 'profile'],
redirectUri: makeRedirectUri({
useProxy,
scheme: 'nudge://',
}),
},
discovery
);
const loginFitbit = async (token) => {
if (token) {
try {
await signInFitbit(token, dispatch);
await storeFitbitToken(token);
setLoggedIn(true);
} catch (e) {
setLoggedIn(false);
addError('Could not login Fitbit. Please try agian later.');
}
}
};
React.useEffect(() => {
if (response?.type === 'success') {
const { access_token } = response.params;
console.log("res",response)
loginFitbit(access_token);
} else {
console.log('error', response);
}
}, [response]);
React.useEffect(() => {
const fetchData = async () => {
let token;
try {
token = await getFitbitToken();
setLoggedIn(true);
} catch (e) {
setLoggedIn(false);
console.error(e);
}
dispatch({ type: 'RESTORE_FITBIT_TOKEN', token: token });
};
fetchData();
}, [dispatch])
If your application type is currently set to using the Authorization Code Grant Flow, access tokens have a default expiration of 8 hours (28800 seconds). This cannot be changed.
However, if you'd like your users to be able to select how long your application can access their data, you will need to change your application settings to the Implicit Grant Flow. This authorization flow allows users to select how long they give consent to your application (1 day, 1 week, 30 days, or 1 year).
https://community.fitbit.com/t5/Web-API-Development/Query-parameter-expires-in-not-working/td-p/3522818
If you want to add extra query params for your auth request you need to add extraParams object with your custom fields.
https://docs.expo.dev/versions/latest/sdk/auth-session/#authrequestconfig
const [request, response, promptAsync] = useAuthRequest(
{
responseType: ResponseType.Token,
clientId: "randomid",
scopes: ["activity", "profile"],
redirectUri: makeRedirectUri({
useProxy,
scheme: "nudge://",
}),
extraParams: {
expires_in: 3600, // <--- new value
},
},
discovery
);
I'm using the last version of MSAL.js (#azure/msal-browser": "^2.23.0"),I can successfully authnenticat but the access token is empty I dont know why.
I'm posting the code here I hope anyone could help please ?
export const useAuthUserStore = defineStore("auth_user", {
state: () => ({
msalConfig: {
auth: {
clientId: "xxx",
authority: xxxxxxxx,
knownAuthorities: [xxxxxxx.onmicrosoft.com],
redirectUri: http://localhost:8080/
},
cache: {
cacheLocation: 'localStorage',
},
},
accessToken: "",
isAuthenticated:false,
}),
get the accesstoken method :
async getAccessToken(){
let request = {
scopes: [https://graph.microsoft.com/offline_access,https://graph.microsoft.com/openid],
// scopes: ['openid', 'offline_access' ],
extraScopesToConsent:['<tenant>.onmicrosoft.com/api/read']
};
const msalInstance = new PublicClientApplication(
this.authStore.$state.msalConfig
);
try {
let tokenResponse = await msalInstance.acquireTokenSilent({
account: this.account ,
scopes: [https://graph.microsoft.com/offline_access,https://graph.microsoft.com/openid]
});
console.log('tokenResponse :',tokenResponse)
return tokenResponse
} catch (error) {
console.error( 'Silent token acquisition failed. Using interactive mode',error );
let tokenResponse = await msalInstance.acquireTokenPopup(request);
console.log(`Access token acquired via interactive auth ${tokenResponse.accessToken}`)
}
},
handleResponse(response) {
console.log('handleResponse.......')
let accountId = "";
const loginRequest = {
scopes: [https://graph.microsoft.com/offline_access,https://graph.microsoft.com/openid],
}
console.log('handleResponse.......',response)
if (response !== null) {
accountId = response.account.homeAccountId;
console.log(accountId)
// Display signed-in user content, call API, etc.
} else {
// In case multiple accounts exist, you can select
const currentAccounts = this.$msalInstance.getAllAccounts();
if (currentAccounts.length === 0) {
// no accounts signed-in, attempt to sign a user in
this.$msalInstance.loginRedirect(loginRequest);
} else if (currentAccounts.length > 1) {
// console.log('handleResponse.......96')
// Add choose account code here
} else if (currentAccounts.length === 1) {
// console.log('handleResponse.......23')
accountId = currentAccounts[0].homeAccountId;
// console.log('handleResponse 111.......',accountId)
}
}
}
the result :
enter image description here
could you help please ? is there any missing code that I have to add ?
Using the acquireToken methods supplied by MSAL, you can receive access tokens for the APIs your app needs to call.
Source Code:- GitHub Sample
For more information please refer the below links:-
SO THREAD|can't get accessToken via requireTokenSilent #azure/msal-browser
I am working on a Next.js project, in which I have included a login system with NextAuth.
Everything was working fine at the beginning, but recently I keep getting an error every time I try to get the session.
The Error:
https://pastebin.com/Mh624N3c
StackOverflow doesn't let me post the whole error, so I had to use Pastebin.
This is the first time I encounter such an error, and I can't seem to be able to find a solution. I am using JWT as the session strategy, if that has to do anything with the issue.
This is the code I use for handling the authentication & session:
await NextAuth(req, res, {
adapter: MongoDBAdapter(clientPromise),
pages: {
signIn: "/login"
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email", placeholder: "example#email.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
const account = await Accounts.exists(credentials.email)
const cryptr = new Cryptr(Config.secret)
const encEmail = cryptr.encrypt(credentials.email)
const url = process.env.NODE_ENV == "development" ? "http://localhost:3000/" : Config.url
if (account) {
const password = cryptr.decrypt(account.password)
if (credentials.password == password) {
return {
...account,
_id: null
}
} else {
return res.redirect("/login?error=true")
}
} else {
const code = await Accounts.requestVerification(credentials.email, password)
const message = {
to: credentials.email,
from: "noreply#bytestobits.dev",
subject: "BytesToBits API - Create Your Account",
html: emailStyle(credentials?.email, url, code),
}
SendGrid.send(message).then(() => console.log("Email Sent"))
return res.redirect("/verify?email=" + encEmail)
}
}
})
],
jwt: {
secret: Config.secret,
encryption: true
},
secret: Config.secret,
session: {
strategy: "jwt"
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.user = user
}
return token
},
async session({ session, token }) {
let data = token.user
if (data) {
if (await Accounts.exists(data.email)) {
data.token = await Accounts.getToken(data.email)
}
data.tokenInfo = await Accounts.tokenInfo(data.token)
}
return data
}
}
})
This happens every time I try to fetch the session or authenticate.
When the user authenticates, a session must be formed, which can be fetched from the client for usage. However, whenever I try to authenticate of fetch the session, a "Parse Error: Header overflow" occurs.
I managed to fix the issue! Turns out the Session object was way too long and caused this error.
Basically in the data's tokenInfo field, it had a really long array. So removing that specific field fixed the issue!
I am using the JWT token to verify my API requests. Access token expires in 1 minute, and refresh token expires in 1 year. After the access token expires, an API request is sent with a refresh token to get a new set of tokens. A new set of tokens are only sent if the refresh token is valid, and exists in the database. I am using Axios interceptor to achieve this. Everytyhing seems to work fine for some time. However, it logs me out even when the refresh token is valid and does exist in DB. I am assuming I am missing something in Axios interceptor or has to do with async functions.
Error logs "code does not match" in server-side at verifyRefreshToken function, and "Error here" in client-side updateToken function.
CLIENT SIDE CODE
API.js
// Response interceptor for API calls
API.interceptors.response.use((response) => {
return response
}, async (error) => {
// reject promise if network error
if (!error.response) {
console.log("Network Error");
return Promise.reject(error);
}
const originalRequest = error.config;
console.log(store.getState().auth)
// if access token is expired
if (error.response.status === 403 && error.response.data.message == "token expired") {
// var refreshToken = await getRefreshToken() // get refresh token from local storage
var refreshToken = await store.getState().auth.refreshToken
// restore tokens using refresh token
await store.dispatch(await updateToken(refreshToken)) // get new set of tokens from server and store tokens in redux state
// var newAccessToken = await getToken() // get token from local storage
var newAccessToken = await store.getState().auth.accessToken
if(newAccessToken != null){
originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
return API(originalRequest)
}
return Promise.reject(error);
}
// if refresh token is expired or does not match
if (error.response.status === 403 && error.response.data.message == "false token") {
socketDisconnect() // disconnect socket connection
signOut() // remove tokens from local storage
store.dispatch(logOut()) // set tokens in redux as null
return Promise.reject(error);
}
return Promise.reject(error);
});
updateTokenFunction
export const updateToken = (rt) => {
return async (dispatch) => {
const data = await API.post('/auth/refreshToken', {
token: rt
})
.then(async res => {
var accessToken = res.data.accessToken
var refreshToken = res.data.refreshToken
await storeToken(accessToken) // store access token in local storage
await storeRefreshToken(refreshToken) // store refresh token in local storage
dispatch(restoreToken({accessToken, refreshToken})) // store token in redux state
})
.catch(err => {
console.log("err here" + err) // LOG SHOWS ERROR HERE
})
}
}
SERVER SIDE CODE
// /auth/refreshToken
// POST: /api/auth/refreshToken
router.post('/', (req, res) => {
var { token } = req.body
if(!token) res.status(403).send({"status":false, "message": "false token", "result": ""})
verifyRefreshToken(token)
.then(async data => {
var userName = data.userName
// get new tokens
var accessToken = await getAccessToken(userName)
var refreshToken = await getRefreshToken(userName)
res.json({"status":true, "message": "token verified", "accessToken": accessToken, "refreshToken": refreshToken})
})
.catch(err => {
console.log(err);
res.status(403).send({"status":false, "message": "false token", "result": ""})
})
});
To generate new refresh token
// generate refresh token
const getRefreshToken = (userName) => {
return new Promise((resolve, reject) => {
var secret = process.env.REFRESH_TOKEN_SECRET
var options = { expiresIn: '1y' }
jwt.sign({userName},secret , options, (err, token) => {
if(err) reject("error")
var data = {"userName": userName, "token": token}
// delete all expired token from database
dbQueries.deleteRefreshToken(data, result => {
})
// add refresh token to database
dbQueries.addRefreshToken(data, result => {
if(result == "success"){
console.log("added token " + token);
resolve(token)
}else{
reject("failure")
}
})
});
})
}
Verifying refresh token
// verify access token
const verifyRefreshToken = (token) => {
return new Promise((resolve, reject) => {
var secret = process.env.REFRESH_TOKEN_SECRET
if(!token) return reject("no token")
jwt.verify(token, secret, (err, user) => {
if(err){
return reject(err)
}
// check if the verified token and token from database matches
var data = {"userName": user.userName}
dbQueries.getRefreshToken(data, result => {
if(result.length == 0){
return reject("no data")
}
if(token === result[0].token){
resolve(user)
} else{
reject("code does not match") // LOGS THIS ERROR
}
})
})
})
}
UPDATE
The error was due to multiple API calls at the same time, and all had requested for a new access token, with old refresh tokens. I solved the issue using code in this link.
I want to display some Twitch datas using Twitch API on my website using React/Node,
I succeeded to display these datas, but at some time the expirationDate property of the token response becomes expired (expirationDate that I chose to put in my localStorage), and the datas don't display anymore,
So, I would need something like this
if (
!localStorage.getItem("expirationDate") ||
localStorage.getItem("expirationDate") < new Date().getTime()
) {
window.location.href = process.env.REACT_APP_TWITCH_ENDPOINT;
}
Here's my full code
useEffect(() => {
authenticate();
}, []);
function authenticate() {
if (
!localStorage.getItem("expirationDate") ||
localStorage.getItem("expirationDate") < new Date().getTime()
) {
window.location.href = process.env.REACT_APP_TWITCH_ENDPOINT;
}
getModerators(
new URL(window.location.href).searchParams.get("code") ||
localStorage.getItem("code"),
localStorage.getItem("accessToken") || "",
localStorage.getItem("expirationDate") || ""
);
}
function getModerators(code, accessToken, expirationDate) {
axios
.post("http://localhost:5000/getModerators", {
code,
accessToken,
expirationDate,
})
.then(function (response) {
localStorage.setItem("expirationDate", response.data.expirationDate);
localStorage.setItem("accessToken", response.data.accessToken);
localStorage.setItem(
"code",
new URL(window.location.href).searchParams.get("code") ||
localStorage.getItem("code")
);
history.push("/");
})
.catch(function (error) {
console.log("error ", error);
});
}
But by doing this, I get in an infinite redirection to the URL...
How can I redirect the user to the URL above if the expirationDate timestamp in localStorage is expired or not defined without getting in an infinite redirection ? Thanks