How to pass MSAL access token to react elastic search App connector - reactjs

AM trying to get a access token for MSAL to pass to our backend API(that eventually calls APP Search). As the access token is from msal promise i cannot initialize the token somewhere globally and access with in other function. is there a way to wait until the promise completed ?
Here is some code:
export default function App() {
const isAuthenticated = useIsAuthenticated();
const {instance, accounts, inProgress} = useMsal();
const { hostIdentifier, searchKey, endpointBase, engineName } = getConfig();
if (!isAuthenticated && inProgress === InteractionStatus.None) {
instance.loginRedirect(loginRequest).catch(e => {
console.error(e);
});
}
const connector= null;
if( accounts[0]){
const request = {
...{scopes:['api://.....']},
account: accounts[0]
};
console.log('Before access token');
getAccessToken(request);
//console.log(`After access token ${accessToken}`);
}
let config;
async function getAccessToken(request){
const accessTokenPromise = await instance.acquireTokenSilent(request);
const searchKey = accessTokenPromise.accessToken;
const connector = new AppSearchAPIConnector({
searchKey,
engineName,
hostIdentifier,
endpointBase,
});
console.log('After COnnector'+connector);
config = {
searchQuery: {
facets: buildFacetConfigFromConfig(),
...buildSearchOptionsFromConfig(),
},
autocompleteQuery: buildAutocompleteQueryConfig(),
apiConnector: connector,
alwaysSearchOnInitialLoad: false,
};
}
return (
<PageLayout>
<AuthenticatedTemplate>
<SearchProvider config={config}>
<WithSearch
mapContextToProps={({ wasSearched }) => ({ wasSearched })}
........
with this code , am getting below error:
Events.js:16 Uncaught No onSearch handler provided and no Connector provided. You must configure one or the other.
any idea , on how to pass access token to App connector ?

Yes, there is a way to wait until the promise is complete. You can use await keyword before the promise and when you use the await keyword you must add the async keyword in function declaration because adding async keyword is required when you use await keyword before the promise code.
When you complete the promise and use the await keyword then you can store the access token in some variable and lastly send it to the app connector.

Related

Logic recommendation for React refreshing token

Can anyone suggest me a good logic to automatically refresh my accessToken?
At the moment, I have an OpenAPI generated class, where the accessToken is a promise in all the requests. In this promise, I check if the token is expired and I fetch the new one, based on a refresh token.
I also have an AuthContext, that I use to manage my authtentication, user details, etc. (saving to localstorage, etc)
The problem is that I need somehow to access my AuthContext and to give it the new token or to logout if the token could not be refreshed, in my API class.
I do receive an error on the following code, but this is expected:
Invalid hook call. Hooks can only be called inside of the body of a function component.
My access token as promise (inside the API class)
accessToken: new Promise<string>(async (resolve, reject) => {
const tokenExpiresAt = Date.parse(model.tokenExpiration);
const {saveAuth, logout} = useAuth() // Here is the problem
// if token is expired, refresh it
if (tokenExpiresAt < Date.now()) {
this.Auth.refresh({accessToken: model.token, refreshToken: model.refreshToken})
.then((response) => {
// save new auth
saveAuth(response.data)
resolve(response.data.token);
})
.catch((error) => {
// error refreshing token, logout
logout()
reject("Token expired");
});
}
resolve(model.token);
})
I faced a similar problem a while ago, yes you cannot call a hook inside a hook so another approach to solve this issue is to intercept the axios response interceptor.
axiosApiInstance.interceptors.response.use((response) => {
return response
}, async function (error) {
const originalRequest = error.config;
if (error.response.status === 403 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = localStorage.getItem('refreshToken');
const access_token = await refreshAccessToken(refreshToken);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + access_token;
return axiosApiInstance(originalRequest);
}
return Promise.reject(error);
});
And you can implement your own refreshAccessToken function according to your usecase.

Using a client-side only, readable store `set`, to refresh API access token

I am experimenting with using a readable store as the auth token for a web app.
The set function would fetch an authentication token from an API, and then set a timeout for X seconds to refresh the token before it expires. This would then continuously run every X seconds to set the token.
// auth.ts
export const ssr = false;
type LoginResponse = {
access_token: string;
expires: string;
}
export const authToken: Readable<string> = readable("", (set) => {
const setAuth = async () => {
const auth: LoginResponse = await authenticate();
set(auth.access_token);
authTimer(auth);
};
const authTimer = async (auth: LoginResponse) => {
const timeRemaining = Math.round(((new Date(auth.expires).getTime() - Date.now())) * 0.9);
setTimeout(async () => {
if (auth.access_token === authToken) {
console.log("refreshing auth token");
setAuth();
}
}, timeRemaining);
}
setTimeout(setAuth, 0)
});
The main issue I'm having is that this set function is called from the backend, causing an exception to raise when the authenticate() call fails. It should only be called once the front-end is loaded, as there needs to be client side headers set for the request to succeed.
Is this a generally good approach or is this not what set is intended for?
Also I seem to be unable to make value comparisons with the value of the readable (e.g. auth.access_token === authToken will always return false).
Finally, how can I queue API calls to only execute when the authToken is not an empty string?

How to Connect Firebase Auth with Google One Tap Login

I have created a web app with Firebase and React.js and implemented sign-in with Google. I then tried to implement GoogleOneTapSignin and the one-tap-sign-in UI is working successfully because I used the react-google-one-tap-login npm package.
If may react app I have a function that listens for AuthStateChange and then either registers the user if they are new or sign in them if they are already a member and also updates the state if they logged. out.
Now that I have implemented google-one-tap-login, I was expecting the onAuthSTaetChanged function to be triggered if a user signs in using the google-one-tap-login but it is not the case.
Below is the part of my App.js code that handles the user auth.
const classes = useStyles();
const dispatch = useDispatch();
const alert = useSelector(state => state.notification.alert);
// Handling Google-one-tap-signin
useGoogleOneTapLogin({
onError: error => console.log(error),
onSuccess: response => {
console.log(response);
const credential = provider.credential(response);
auth.signInWithCredential(credential).then(result => {
const {
user
} = result;
console.log(user);
});
},
googleAccountConfigs: {
client_id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
});
//Handling firebase authentification
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
// If there is a user create the user profile and update useState
if (user) {
// createUserProfile function creates the user profile in firestore if they are new
const userRef = await createUserProfileDocument(user);
userRef.onSnapshot(snapshot => {
const doc = snapshot.data();
dispatch(
setUser({
id: snapshot.id,
...doc
})
);
});
} else {
dispatch(setUser(null));
}
});
return () => {
unsubscribe();
};
}, [dispatch]);
I tried to implement the solution suggested by the 2nd answer in this StackOverflow question but I get the error below on the console. when I use google-one-tap-sign-in. Remember I am not using the FirebaseUi library. So far my application only uses the sign in with Google
t {
code: "auth/argument-error",
message: "credential failed: must provide the ID token and/or the access token.",
a: null
}
a: null
code: "auth/argument-error"
message: "credential failed: must provide the ID token and/or the access token."
The ID token required by Firebase's signInWithCredential function exists within the credential property of the response object. Here is a sample function below using Firebase V8.
// firebase V8
function handleCredentialResponse(response) {
if (response) {
const cred = auth.GoogleAuthProvider.credential(response.credential);
// Sign in with credential from the Google user.
return auth().signInWithCredential(cred);
}
}
Firebase v9
// firebase V9
import { getAuth, GoogleAuthProvider, signInWithCredential } from "firebase/auth";
const auth = getAuth();
function handleCredentialResponse(response) {
if (response) {
const cred = GoogleAuthProvider.credential(response.credential)
// Sign in with credential from the Google user.
return signInWithCredential(auth, cred);
}
}
The response param is a credential response returned from the Google one-tap function callback.
google?.accounts.id.initialize({
client_id: your-google-app-client-id.apps.googleusercontent.com,
callback: handleCredentialResponse,
});
google?.accounts.id.prompt((notification) => {
console.log(notification);
});

How to make componentWillMount to await until token is stored in local storage

I am trying to store jwt token in local storage on app startup but the render method gets called even before the token is stored in local storage. Could you please help to componentWillMount to wait until token is stored in local storage?
Index.js
componentWillMount() {
AuthRepository.getToekn();
}
AuthRepository.js
class AuthRepository {
constructor(callback) {
this.callback = callback;
}
async getToekn() {
const reponse = await axios
.post(`${baseUrl}/auth/local`, {
identifier:(process.env.IDENTIFIER).trim(),
password:(process.env.IDENTIFIER_PWD).trim(),
})
.then((response) => {
setTrapiToken(response.data.jwt)
return response.data.jwt;
})
.catch((error) => ({ error: JSON.stringify(error) }));
return reponse;
}
}
export default new AuthRepository();
Function to set token
export const setTrapiToken = (token) => {
console.log('here')
console.log(token)
try{
localStorage.setItem(process.env.JWT_KEY,token);
return true;
}catch(e){
console.log('false')
return false;
}
};
React's rendering never waits - and so mounting also never waits.
If you like it or not, that component is mounted. So all you can do is check if the data is already in place and display some kind of loading indicator if it isn't yet.

Token is Invalid in Reactjs application

I try to get a list from the backend in Reactjs component with JWT token but I get an error message {"status":"Token is Invalid"}, please guide me.
My backend API is working fine and my token is saved in the localstore after login.
my frontend used API code
import {API} from "../config";
/**
* to get about pages
* get a single about page
* update a about page
* delete about page
*/
export const getAboutContent = (token) =>{
return fetch(`${API}/about/?token=${token}`, {
method: "GET",
})
.then(response =>{
return response.json();
})
.catch(err =>{
console.log(err);
});
};
about/index.js
const [allAboutContent, setAllAboutContent] = useState([]);
const loadAllAboutContent = () => {
getAboutContent().then(data => {
if(data.error){
console.log(data.error)
} else{
setAllAboutContent(data.data)
}
});
};
useEffect(() =>{
loadAllAboutContent();
}, [])
Please help.
You are invoking getAboutContent in about/index.js file without JWT and hence it not defined. Just update your code to read JWT from localStorage like below
const loadAllAboutContent = () => {
// Read token from local storage
const token = localStorage.getItem('jwt');
// Pass to getAboutContent
getAboutContent(token).then(data => {
if(data.error){
console.log(data.error)
} else{
setAllAboutContent(data.data)
}
});
};
Also, I see you have stored your token as {token: ''}. Maybe, you can directly save it. Otherwise you have to read it like this JSON.parse(localStorage.getItem('jwt')).token

Resources