I am using react-admin with simple graphql dataprovider. On backend I block all requests without token except my login endpoint. I use authProvider to make a request to login backend. However React-admin still use dataprovider on authentication page and since I don't send an authentication token in the header before login I get an error.
Expected behavior should be: React-admin not using graphql dataprovider while loading login page.

If I allow unauthenticated requests to /graphql in the backend, I successfully see the login page.

As you see, react-admin is making a request to data-provider while loading the login page. But this is an issue since grahpql provide has only one endpoint /graphql
Here is the code that encounters the issue:
const httpLink = createHttpLink({ uri: 'http://localhost:8080/graphql' })
const authLink = setContext((_, { headers }) => {
const token = Cookies.get('accessToken')
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
buildGraphQLProvider({
client
})
.then(dataProvider => this.setState({ dataProvider }));
So react-admin graphql dataprovider needs to access /graphql to load successfully, but that is the only endpoint for all other queries as well. I need to secure that endpoint.
Some other problem on the other side. My client looks the same, but it works:
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';
import { getAccessToken } from './getAccessToken';
import { URI_AUTH } from '../appsettings';
const httpLink = createHttpLink({
uri: URI_AUTH
});
const authLink = setContext(async (_, { headers }) => {
const token = await getAccessToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
export const apolloAuthClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export const apolloAuthClientWithAuth = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
@wmwart how does your backend look like? Do you block /graphql for unauthenticated requests?
Here is my dirty and ugly fix for the problem
const httpLink = createHttpLink({ uri: 'http://localhost:8080/graphql' })
const authLink = setContext((_, { headers }) => {
const token = Cookies.get('accessToken')
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
try {
const dataProvider= await buildGraphQLProvider({
client
})
this.setState({ dataProvider })
} catch (error) {
console.log(error)
Cookies.remove('accessToken');
window.location.replace("/")
}
I created react-admin inside another app and in the "/" I do login separate from react-admin.
what do you mean block /graphql for unauthenticated requests?
On all requests or mutations that require authentication, I use the withAuth directive, which returns an error if there is no token or it is not valid. In general, this is not a react-admin issue. Something with the logic of your application.
Below is the middleware that I use, all pages except /subscriptions/login page responds with 401, if there is not a Auth token in the header, which means, if there is a request to /graphql without a token, I respond with 401. However, react-admin login page still make a request to /graphql with graphql-data-provider. If it was a REST Api with different endpoints This would not be an issue. Issue is react-admin grahpql data provider makes a request to /graphql while loading login page. which it shouldn't make this request.What is the withAuth directive? Can you show me your code?
```export class TokenMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
const authJsonWebToken = req.headers.authorization;
if (req.baseUrl !== '/subscriptions/login') {
if (!authJsonWebToken) {
console.log('invalid')
throw new HttpException('Invalid Token', HttpStatus.UNAUTHORIZED)
} else {
try {
const store = jwt.verify(authJsonWebToken.slice(7, authJsonWebToken.length), 'mySecretKey');
if (store) {
req['store'] = store;
next();
}
} catch (error) {
throw new HttpException('Invalid Token', HttpStatus.UNAUTHORIZED)
}
}
}
else {
next();
}
}
}```
However React-admin still use dataprovider on authentication page
I don't get this. The login form uses the login hook which does not call the dataProvider but the authProvider.
Besides, so far, none of the graphql packages deal with authentication.
I think this is more an how to question than an actual bug in react-admin and I invite you to look for answers on StackOverflow.
Well thats the expected behavior, however, it is making a request to /graphql data provider while loading the login page. This is before user putting credentials, this happens while loading the login page. For security reasons if I block all requests to my /graphql endpoint without access token, react admin crashes. This is an Actual Bug
Please install a graphql data provider and go to login page, watch your Network tab and you will see that it makes a get request (probably getting schema) to /graphql
Ah yes, you're right. Not sure about what we can do here but I'll mark this as an enhancement. However, please note that our graphql dataProviders are mostly just examples. Use them as a starting point to implement your own.
I did a workaround to encapsulate react-admin around another react app and everything works fine.
That's one way to do it but I think fixing it in the provider itself would probably be better. Postponing the introspection call until the first real dataProvider call (on a resource) for example
That would be ideal
@arrrrny Did you find any solution for this issue. I am also having the same problem. Any help would be appreciated.
@djhi How about just allowing a null dataProvider if authProvider is provided and the user isn't currently logged in? This way we could just initialize and set the dataProvider in the login() method.
Instead of using the introspection query, you can also pass the introspection result directly when you create the provider. If your server blocks access to the schema without authentication, it makes sense to generate the introspection result ahead of time.
You can use graphql codegen (https://graphql-code-generator.com/) for that
.codegen.yml
overwrite: true
schema: 'http://localhost:4000/graphql'
generates:
./your/path/introspection.json:
plugins:
- introspection
Then import the introspection.json file in your project and initialize the provider with the schema:
See https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql#introspection-options
Most helpful comment
You can use graphql codegen (https://graphql-code-generator.com/) for that
.codegen.ymlThen import the
introspection.jsonfile in your project and initialize the provider with the schema:See https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql#introspection-options