Environment:
This is code I'm trying to implement on top of the tried and tested React-boilerplate.
While this boilerplate is wonderful, it doesn't contain authentication. I'm trying to implement my own authentication logic in a way that meshes with the existing logic of the boilerplate.
Context:
I have a service/utility function that is used to send the server requests with a JWT authentication header and return its response.
Goal:
I'm trying to implement logic where when a request is made using an expired token, the access token is automatically refreshed before the request is sent to the server.
What's stopping me:
I have a saga that handles the access token refresh. It works perfectly when it is called from within a container. But because this is a service, it is unaware of the redux store. For this reason I am unable to dispatch actions.
The react-boilerplate structure works by injecting reducers and sagas on the fly. This means I'd need to inject the authentication saga and reducer from within the service (kinda feels wrong no?). However, the saga and reducer injectors are React side-effects and can, therefore, only be used inside of React components.
I feel like the task I'm trying to achieve is quite trivial (and I'm sure it was implementing a million times already), and yet, I can't think of a solution that doesn't seem to go against the entire logic of why use Redux or Sagas to begin with.
Can anyone offer some insights? In the attached image, the red text is the part I'm struggling to implement
See code below:
/**
* Requests a URL, returning a promise
*
* #param {string} url The URL we want to request
* #param {object} [options] The options we want to pass to "fetch".
*
* #return {object} The response data
*/
export default function request( url, options ) {
const token = makeSelectAccessToken();
if (!token) throw new Error('No access token found.');
// Refresh access token if it's expired.
if (new Date(token.expires) - Date.now() <= 0) {
// TODO: Attempt to use refresh token to get new access token before continuing
/** dispatch(REFRESH_ACCESS_TOKEN) **/
// PROBLEM: can't dispatch actions because the store is not exposed to this service.
// Secondary challenge: Can't inject Saga or Reducer because the React-boilerplate injectors are React side-effects.
}
options = {
...options,
Authorization: `Bearer ${token.token}`, // Adding the JWT token to the request
};
return fetch(url, options)
}
There is multiple way to do that
1- Export the store
create new store.js
import { createStore } from 'redux';
import reducer from './reducer';
//you can add your middleware, sagas, ...
const store = createStore(reducer);
export default store;
import it to your service and use it everywhere but you’ll end up with a single store for all of your users
Cons: you’ll end up with a single store for all of your users(if
your app is using Server Side Rendering don't use this method)
Pros: Easy to use
2- You can add your func to middleware and intercept an action
edit your create store code and add new middleware for authorization
const refreshAuthToken = store => next => action => {
const token = makeSelectAccessToken();
if (!token) throw new Error('No access token found.');
if(new Date(token.expires) - Date.now() <= 0) {
// you can dispatch inside store
this.store.dispatch(REFRESH_ACCESS_TOKEN);
}
// continue processing this action
return next(action);
}
const store = createStore(
... //add to your middleware list
applyMiddleware(refreshAuthToken)
);
Cons: Maybe you can need redux-thunk library if this middleware not solve your problem (its easy job. You can't say this even a con)
Pros: Best and safer way for your need and it will work everytime you call an action. It will works like a charm on SSR apps
3- or you can send store as parameter to your request method from component
Cons: Not the best way it will run after your components initialize and you can make easily mistake or can forget to add to your component.
Pros: At least you can do what you want
Maybe a better approach would be to store the token on the localStorage so that you don't need to access the Redux store to obtain it. Just fetch the token and store it this way:
localStorage.setItem('token', JSON.stringify(token))
Later in your service, just retrieve like this:
const token = JSON.parse(localStorage.getItem('token'));
If you don’t feel safe storing the token in the local storage, there's also the secure-ls package, that allows encryption for the data saved to the local storage.
Related
I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.
I’m using next.js to build static HTML webpages.
One of my webpages needs data from a third-party API, which I’d like to fetch at build time and bake into the resulting HTML.
I don’t want this call to ever happen on the client, because:
CORS prevents the request from succeeding anyway
I would have to expose an API key on the client (no thank you)
I thought getInitialProps was the answer, because the fetched data is indeed baked in during the build/export process, but when I navigate away from the page and return from it, getInitialProps gets triggered on the client, breaking everything.
My current code in getInitialProps is something like:
static async getInitialProps(){
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
}
Any advice or best practice on how to handle this? I know Gatsby.js does it out of the box.
one possibility would be, if you just want to execute this once on the server to check if the req parameter is present in getInitialProps: (Documentation)
req - HTTP request object (server only).
One dirty approach:
static async getInitialProps({ req }){
if (req) {
// only executed on server
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
} else {
// client context
}
Hope this helps a little bit.
I am working on an Electron/React/Redux app that uses NeDB for a local data store (though this problem is not really about NeDB I think). I can use ipcRenderer.send to make calls to load the local data store but I am unclear on how to do this in the context of a redux action creator since the way I get the data back is via the ipcRenderer.on listener.
An action creator that obviously doesn't work...
export function getData() {
ipcRenderer.send('getData');
return {
type: GET_DATA,
payload: // WHAT IS THE PAYLOAD?
}
}
I am trying to wrap my head around RXJS and had a quick question on how to tackle the following workflow using observables instead of promises.
Here is the log in workflow in angular 2:
i look in local storage for JWT. if JWT exists i return the token.
if not in local storage i check to see the platform. if Android i log into my server using google bearer token, my server returns a JWT (through angular HTTP obserable).
if not in local storage and the platform is a windows computer i generate a JWT on my server and return (through angular HTTP obserable)
for the two calls to the server i want to cache the token in local storage before returning the JWT information to the calling function.
I have everything execpt how to do this properly using RXJS, mostly how to chain all of this together. Can i get a quick pseudocode using Observables on how to do this? I basically want to intercept the JWT and store in local storage before moving on in the application (the calling function subscribing to the sequence above)
Any help would be great!
I would prefer async functions for this task.
var jwtPromise = null;
function getJwt() {
return jwtPromise || (jwtPromise = getJwtNotCached());
}
async function getJwtNotCached() {
const localJwt = getLocalJwt();
if (localJwt) {
return localJwt;
}
const newJwt = await fetchJwtByPlatform();
storeLocalJwt(newJwt);
return newJwt;
}
async function fetchJwtByPlatform() {
if (platformIsAndroid()) {
return await fetchJwtOnAndroid();
}
return await fetchJwtOnWindows();
}
...
This code will even ensure that no multiple network requests are made if called twice at a time.
I'm using vanilla flux with some utils for communicating with my APIs.
On initial page load I'd like to read some token from local storage and then make a request to my API to get my data.
I've got a LocalStorageUtils.js library for interacting with window.localStorage. My container component handles all login/logout actions and reads the current user on page load.
App.js
componentWillMount() {
LocalStorageUtils.get('user');
}
LocalStorageUtils reads the value and brings it into Flux via ServerAction similar to the flux chat example.
get(key) {
var value = window.localStorage.getItem(key);
if (value) {
ServerActionCreators.receiveFromLocalStorage(key, value);
}
}
That puts the user into my UserStore and into my views where I can show the username and some logout link, etc.
I also have ApiUtils.js for requesting data from the server. So the question is: Where do I tell my ApiUtils that I have a logged-in user at initial page load?
I could call some method inside ApiUtils from my LocalStorageUtils but that does not feel right.
Or do I have to make another round trip whenever I get a change event inside my container component?
You should pass the user as data to your ApiUtils class and removing the need from being concerned about how your ApiUtils is used.
var ApiUtils = function () {
this.get = function (endpoint, data) {
return theWayYouSendAjaxRequests.get(endpoint).setData(data);
};
};
// Wherever your ApiUtils is used.
var api = new ApiUtils();
api.get('/me', {user: userFromStore});
I found a solution that works and feels right.
As getting data from local storage is synchronous there is no need to pipe it through Flux via ServerActionCreators. Simply use LocalStorageUtils to get the current user and call the login method with that user. The login Action is the same you would use when a user initially logs in. login triggers getting new data from server. The new data is saved in my DataStore and the user is saved to my UserStore.
Thanks for all hints at Twitter and at https://reactiflux.slack.com/.