I'm a beginner in react native and I'm creating an app. I've done some research about how to make a secured react native app, but I didn't found much information. I've come up with a "solution" myself, but I want to make sure this is the right way to do this. So I need the help of some react native/javascript/security experts if possible, to quickly check if my approach is OK or not?
I have included 3 questions in this text, but obviously they're related. I've put them in bold. Feel free to answer one or more questions, I appreciate every answer!
I'm creating an app in react native. For a user to be able to use the app, the user should create an account and sign in. I'm using an JSON web token as an access token to authorize the requests made from the app to the server, and to identify the user (I store the user ID in the JSON web token).
At my server, I first check if the access token is valid. If so, I get the user ID out of the access token and use this user ID to identify the user.
For extra security, I'm also using refresh tokens, because an access token is only valid for 10 minutes. When a user send a request with an expired access token, the server responds with a 401 not authorized status.
To make my code more "managable", I've created a wrapper function in react native. I wrap every "request function" (every function where I do a GET/POST/PUT/DELETE request to the server) with this wrapper function. This wrapper function checks the response of the request. If the response status is 200, the response is returned to the code. If the response status is 401, the refresh token is send to a specific endpoint to obtain a new access token. When the access token arrives at the app, the previous request is made again with the new access token. The wrapper function also stores the new access token in (temporary) redux (keychain or shared preferences). 1. Is a wrapper function a good idea? For me, it's more manageble because now I'm reusing the code.
Every time the user opens the app, a new access token is requested, and when a user closes the app, the current access token is deleted, even if it is not expired yet. That way, I want to make sure that every app "session" starts with a new access token. 2. Is this okay? Or should I prevent unnecessary requests to the server when I still have a (possibly) valid access token?
In my react native app, this wrapper function is located in a context component. This "authentication" context is wrapper around my other components in App.js like this:
<AuthenticationProvider>
<AppNavigator />
</AuthenticationProvider>
This way, my wrapper function is accessible to all my other components. My authentication context looks like this:
const AuthenticationContext = createContext({
accessToken: null,
wrapperFunction: () => {}
})
const AuthenticationProvider = (props) => {
let accessToken = null
const refreshToken = useSelector(state => state.auth.refreshToken)
const wrapperFunction = () => {
// wrapper function
// set the access token
// await fetch('server endpoint')...
}
return (
<AuthenticationContext.Provider value={{ accessToken, wrapperFunction }}>
{props.children}
</AuthenticationContext.Provider>
)
}
3. Is using a context a good practice to do stuff like this?
Server-side, I store every refresh token in a database. When a user requests a new access token, I check if the sent request token still exists in the database. If not, I have revoked access for this user and the user should be logged out. This way, I want to make sure I can "manage" users.
Yes, it makes sense. Actually I can't think of a better way to manage the scenario you mentioned. When you wanna temper the request before it's sent, you will need a single function to do so. You could also use some hooks e.g. onBeforeSend and onAfterReceive, but in your case I don't see any extra value for this.
I do not agree with the deletion of a valid token. You can still send request to server on every app start to get user's last data -might have changed on another device-. I don't understand the logic of starting the app with a new session -maybe more information?
I don't think you need to pass the wrapperFunction/token using context. It would be best if you could send user data by context. you wrapper function can access the token directly from asyncStorage. And each component can call the function directly by importing it.
I believe you are taking the approach of using a wrapper function since the relevant API requests are made directly in components. The best practice is to move such requests outside (E.g. Redux actions with a middleware like redux-thunk) the components.
It's better to check if the access token is expired (by decoding the token) before sending the API request and retrieve the new access token. This will reduce the amount of requests to server. You can implement a common request method which handle this check as well.
I think since your access token expires every 10 mins this is unnecessary. Is there a specific reason to start each session with a new access token?
You can pass in user access details using the context. I think it's matter of preference. Passing in the wrapper function is not needed if you're handing the requests through a common request method.
Related
I am currently building an app using the following
Next.js
Express API
Redux-Toolkit & RTK Query
I have all of the authentication logic implemented, but have ran into an issue.
So, upon successful login, the express api returns 2 httponly cookies containing the access/refresh tokens.
I have an endpoint in my api to get the current user using the access token i.e /api/auth/me
This all works fine, but what I can't figure out is the best way to fetch the user on each page load and store them in the redux state.
Do I use RTK Query to hit the /api/auth/me endpoint and just call the query whenever I need it throughout the app?
Ideally I fetch and set the user in _app.tsx, but I cannot use redux dispatch since it's outside of the <Provider store={store}></Provider>
Also for example if I wanted to use the redux stored user in getServerSideProps I can't seem to do that either because it's not client side and doesn't have access to redux.
I just can't seem to find a good way to simply set the authenticated user and be able to use them globally throughout the app whether it's inside redux store or in something like getServerSideProps.
Any advice would be truly amazing, I am totally lost.
One way of persisting authentication state is storing the token in the local storage. And in the main App component you can check whether the token is present in the redux store. If it's not there you can set it manually in useEffect function.
As such:
useEffect(() => {
fetchAuthTokenFromLocalStorage().then((token) => {
dispatch(setAuthenticationResult(token));
});},[dispatch]);
I'm building a react native app which uses the spotify web api. I'm using the authorization code flow to authorize a user. First I get a authorization code which can be used to obtain an access token and a refresh token. Everything works!
The problem is: an access token is only valid for a limited amount of time. That's where the refresh token comes in. I understand this concept, but I'm breaking my head about how to implement this.
Let's say a users opens the app, requests an access token and uses this for some time. Then, the user closes the app. After 15 minutes, the users opens the app again. The access token has now expired, so I need to request a new access token.
I've come op with several "solutions". Can someone point me to the correct solution?
Solution 1:
Every time the user opens the app, I request a new access token and use this. Problem: when the user uses the app longer than the valid time of the access token, I won't work anymore.
Solution 2:
I use the access token that's stored in the secure storage on every request. When a request comes back with 'access token invalid' (I don't know the exact error code but you guys know what I mean), I request a new access token with the stored refresh token, and then I send the previous command again (with the new access token). But my question here is: can I use some kind of "wrapper function" which checks the response of the request, and if the response is "access token invalid", it automatically requests a new access token and runs the previous request again.
I think certainly correct solution is solution 2,and i think its clear enough.
and for using solution 2 you need somthing like wrapper function,yes its intelligently.
so you should use interceptor:
what is interceptor ?
You can intercept requests or responses before they are handled by then or catch.
in link below there is a good example of implementing refresh token in axios interceptor:
https://gist.github.com/Godofbrowser/bf118322301af3fc334437c683887c5f
I agree that Solution 2 is the best, each time you do a request you can check to see if the Access Token has expired, and if it has then you can request a new Access Token using the Refresh Token as you mentioned and then make your request, in my own project I do this in a FormatRequestHeadersAsync method which calls a CheckAndRenewTokenAsync method where I perform the following check, here shown in C#:
if(AccessToken?.Refresh != null && (AccessToken.Expiration < DateTime.UtcNow))
{
AccessToken = await GetRefreshTokenAsync(
AccessToken.Refresh,
AccessToken.TokenType,
cancellationToken);
}
You can store the Access Token and the Refresh Token and then use something similar to this before you make each request to the API this will refresh your token and then you can store the new Access Token and the existing Refresh Token.
I am building an app using Nativescript-Vue that requires authentication of users in order to use the app. I have a RESTful backend that functions appropriately as tested with Postman.
JWT Tokens are implemented with a perpetual life but require refreshing every 5 minutes (refresh functionality - in a vaccuum -- working appropriately).
Using Axios.js for web calls.
I am stuck on how to implement basic logic for determining if the user is logged in. All Axios calls return a Promise. I have read the extended "Promise" responses a bunch, but it's not sinking into my head how to do what I want. In a nutshell, I need to pause code execution until the API can authenticate the user and this is not computing for me.
Code as follows:
app.js
// Import VUE library
import Vue from "nativescript-vue";
// This is my user handler
import {UserServices} from "./assets/js/UserServices.js"
// imported Components
import Login from "./components/Login";
import Home from "./components/Home.vue"
let user = new UserServices();
let loggedIn = user.checkAuthStatus();
new Vue({
render: h => h('Frame', [
h(
loggedIn ? Home : Login
)
])
}).$start();
This isn't working because user.checkAuthStatus() is an async function that returns a promise and thus I cannot get a boolean value returned. I know this is the problem, but I left that in the code so that the intended result can be understood. What I don't understand is how to rewrite the code so that the designed flow is feasible using Promises.
Core logic is designed to be:
Check the user's logged-in status via user.checkAuthStatus(). This routine checks for a valid token (valid meaning it exists and is not expired). If it is expired, the token is refreshed via a call to this.refresh() from the UserServices controller.
If a value of "true" is returned from user.checkAuthStatus() the Vue app should load the Home component (aka user is logged in), else the user should be required to login.
I can only imagine that this is a simple situation thousands of people have successfully overcome, but my brain isn't working thru it. I get why JS needs to continue running so as not to stop the progress of the code (and that's the point of a Promise, I think?), but sometimes the code just needs to stop and wait it seems, like in a user authentication scenario.
Any help drilling down on the specifics on how to address my challenge? Please and thank you.
I would do like this:
Check if a locally stored token exists, valid and not expired, if yes go to homepage else redirect to login.
Prior to every call made to the backend, I used Auth.currentAuthenticatedUser() to obtain idToken.jwtToken and pass it in the header of my request to the backend server for data.
Is there a difference between using Auth.currentSession() instead of Auth.currentAuthenticatedUser() for my use-case? Does Auth.currentAuthenticatedUser() refresh the token once it has expired, similar to Auth.currentSession()?
The documentation for amplify auth is still very poor, so I looked into the source code for #aws-amplify/auth and amazon-cognito-identity-js packages and these are the findings:
currentAuthenticatedUser will try to retrieve authenticated user info from localstorage (unless your storage options is configured otherwise). If it doesn't exist in storage, then it will make api calls to retrieve user info which involves automatically refreshing the user session in the process.
currentSession will not check the local storage and always invoke the API which also involves automatically refreshing the user session if expired.
So to answer your question directly, the Auth.currentAuthenticatedUser() method doesn't always give you a valid token. If your storage contains an expired token, it will just return that. This would require you to call user.getSession() on the returned user object to request for a new session/token manually. I recommend that you use Auth.currentSession() since this handles the token refresh automatically and always returns a valid token.
I'm figuring out the data-flow, and writing action oriented code to deal with Redux.
This is the flow:
I reach SplashScreen and check if accessToken exists. If no, I send the user to LoginScreen. Else, I take him to the HomeScreen.
The flow is rather simple but I'm not able to wrap my head around how to store the token, expire the token or just check if user is logged in. So, technically, when a user logs in, the state(access token) should change. I'm not asking for code, just some pseudo-code and a little explanation will help!
If you want to user the redux store to save token or any user credentials you will need to ensure the state is saved on localStorage, and for that you can use a redux middleware like this or this, or you can use middle-ware of your own :
export default store => next => action => {
let result = next(action)
localStorage.state = JSON.stringify(store.getState())
return result
}
I don't know if you are using react router, If you are, take a look at this example: authenticator I think it's really the best way to do it..
In my app I used react-native-keychain. When a user logs in, I store the accessToken in the redux store. From there, any time I'm making a request, I just get the token from the store, ensure that it's valid, and make the request.