Before I start, I'm very new to React but not so to AWS which has resulted in me picking off a project above my ability set it appears.
I'm building a very simple CRUD website which has a list of posts and a list of users who made those posts. I started off following this tutorial here https://marmelab.com/react-admin/Tutorial.html once at the end I started extending it out into an AWS deployment.
Ostensibly an S3 bucket with the React site, and API to provide the data interfaces all behind a cloudfront distribution.
I don't want every man and his dog to have access so I want to implement a Cognito authentication provider infront of my service, like this:
My issue is that the tutorial walks me through setting up a dataProvider using the ra-data-json-server
module. The ra-auth-cognito
module provides what I need for logging in and works correctly, valid Cognito users can log-in to the dashboard, though they cannot access any data via the API calls.
My App.tsx file contains
...
import { CognitoAuthProvider, Login } from 'ra-auth-cognito';
import { CognitoUserPool } from 'amazon-cognito-identity-js';
const userPool = new CognitoUserPool({
UserPoolId: "us-east-1_MyPoOlId",
ClientId: "MyClIeNtId",
});
const authProvider = CognitoAuthProvider(userPool);
export const App = () => (
<Admin authProvider={authProvider} dataProvider={dataProvider} dashboard={Dashboard} loginPage={Login} >
<Resource name="posts" list.......
My dataProvider.ts looks like
import jsonServerProvider from "ra-data-json-server";
// I know i need a httpClient function here (I think)
export const dataProvider = (() => {
return jsonServerProvider("https://my-url/api/");
})();
I've been working through the documentation https://marmelab.com/react-admin/Authentication.html which I understand lets me pass an httpClient in to the jsonServerProvider as a second parameter, the provided code block results in the error "The dataProvider threw an error. It should return a rejected Promise instead"
I've also tried following what this post advised incorporating ra-cognito in react-admin with ra-data-json-server promise problem though this user is using Amplify which I am not however it doesn't work for me regardless
The issue is, I can now protect the admin panel, but doing anything which calls the backend API with authentication enabled isn't being sent the authentication, the API is expecting a Bearer Token which correctly replies with the data expected when I hit it through postman with a valid Bearer token.
How do I link the calls to the API in such a was that the logged in token is passed thro to the api calls made by the ra-data-json-server
Kudos for providing a detailed question to begin with :)
I believe you are missing two things to get it to work.
ra-auth-cognito
almost does it internally, but unfortunately it does not document how to obtain the JWT token for the currently logged user. Here's one way to get it (inspired by authProvider.ts
):
const getJwtToken = async () => {
return new Promise((resolve, reject) => {
const user = userPool.getCurrentUser();
if (!user) {
return reject();
}
user.getSession((err, session: CognitoUserSession) => {
if (err) {
return reject(err);
}
if (!session.isValid()) {
return reject();
}
const token = session.getIdToken().getJwtToken();
return resolve(token);
});
});
}
I believe using a custom httpClient
is indeed the best approach here. You almost had it working :).
Here is an example adapted from the ra-data-json-server
documentation:
import { fetchUtils, Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const getJwtToken = async () => { /* ... */ };
const httpClient = async (url: string, options: any = {}) => {
options.user = {
authenticated: true,
token: await getJwtToken(),
};
return fetchUtils.fetchJson(url, options);
};
const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com', httpClient);
render(
<Admin dataProvider={dataProvider} title="Example Admin">
...
</Admin>,
document.getElementById('root')
);
This should be enough to include the Bearer Token to each API call.
Hope this helps! :)