xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago

404 after logging in

Why does refine application show a 404 after signing in? I have a custom auth provider(keycloak.js) and this started happening after I injected it into refine application. As a framework I thought refine should not care what that auth provider does, whenever you hit a route like on screenshot refine should take control and redirect you if you're logged in. And it actually happened before when there was no keycloak auth provider. But now this happens. Also, as a matter of fact, I have a custom catchAll page(basically the one you see on screenshot) and it's code will be below. Should this behaviour somehow be moderated in catchAll component?
No description
62 Replies
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
The code for catchall component:
import { Info } from '@mui/icons-material';
import { useLogout, useNavigation, usePermissions } from '@pankod/refine-core';
import { Button, Grid, Stack, Tooltip, Typography } from '@pankod/refine-mui';
import StatusCodes from 'http-status-codes';

const ErrorComponent = () => {
const { data: isAdmin } = usePermissions<boolean>();
const { push } = useNavigation();
const { mutate } = useLogout();

const logout = () => mutate();

const goHome = () => push('/');

return (
<Grid display="flex" justifyContent="center" alignItems="center" mt={20}>
<Grid container direction="column" display="flex" alignItems="center">
{isAdmin ? (
<>
<Typography variant="h1">{StatusCodes.NOT_FOUND}</Typography>
<Stack direction="row" spacing="2">
<Typography>
Sorry, the page you visited does not exist.
</Typography>
</Stack>
<Button onClick={goHome}>Back Home</Button>
</>
) : (
<>
<Typography variant="h1">{StatusCodes.FORBIDDEN}</Typography>
<Stack direction="row" spacing="2">
<Typography>
Sorry, you don't have access to this page.
</Typography>

<Tooltip
title={
'Please contact admin panel administrators to grant you access.'
}
>
<Info data-testid="error-component-tooltip" />
</Tooltip>
</Stack>
<Button onClick={logout}>Log out</Button>
</>
)}
</Grid>
</Grid>
);
};

export default ErrorComponent;
import { Info } from '@mui/icons-material';
import { useLogout, useNavigation, usePermissions } from '@pankod/refine-core';
import { Button, Grid, Stack, Tooltip, Typography } from '@pankod/refine-mui';
import StatusCodes from 'http-status-codes';

const ErrorComponent = () => {
const { data: isAdmin } = usePermissions<boolean>();
const { push } = useNavigation();
const { mutate } = useLogout();

const logout = () => mutate();

const goHome = () => push('/');

return (
<Grid display="flex" justifyContent="center" alignItems="center" mt={20}>
<Grid container direction="column" display="flex" alignItems="center">
{isAdmin ? (
<>
<Typography variant="h1">{StatusCodes.NOT_FOUND}</Typography>
<Stack direction="row" spacing="2">
<Typography>
Sorry, the page you visited does not exist.
</Typography>
</Stack>
<Button onClick={goHome}>Back Home</Button>
</>
) : (
<>
<Typography variant="h1">{StatusCodes.FORBIDDEN}</Typography>
<Stack direction="row" spacing="2">
<Typography>
Sorry, you don't have access to this page.
</Typography>

<Tooltip
title={
'Please contact admin panel administrators to grant you access.'
}
>
<Info data-testid="error-component-tooltip" />
</Tooltip>
</Stack>
<Button onClick={logout}>Log out</Button>
</>
)}
</Grid>
</Grid>
);
};

export default ErrorComponent;
Omer
Omerβ€’17mo ago
Hey @metammodern , This is not expected behavior. Can you share your AuthProvider?
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Sure, here is authProvider:
import { AuthProvider } from '@pankod/refine-core';
import UserService from 'services/UserService';

/**
* @doc https://refine.dev/docs/api-reference/core/providers/auth-provider/#creating-an-authprovider
*/
export const authProvider: AuthProvider = {
login: () => {
UserService.doLogin();
return Promise.resolve();
},
logout: () => {
UserService.doLogout();
return Promise.resolve();
},

checkAuth: () => {
return UserService.isLoggedIn() ? Promise.resolve() : Promise.reject();
},

getPermissions: () => {
return Promise.resolve(UserService.isAdmin());
},
checkError: (error) => {
if (error.status === 401) {
return Promise.reject();
}
return Promise.resolve();
},
getUserIdentity: () =>
Promise.resolve({
name: UserService.getName(),
avatar: UserService.getPicture(),
}),
};
import { AuthProvider } from '@pankod/refine-core';
import UserService from 'services/UserService';

/**
* @doc https://refine.dev/docs/api-reference/core/providers/auth-provider/#creating-an-authprovider
*/
export const authProvider: AuthProvider = {
login: () => {
UserService.doLogin();
return Promise.resolve();
},
logout: () => {
UserService.doLogout();
return Promise.resolve();
},

checkAuth: () => {
return UserService.isLoggedIn() ? Promise.resolve() : Promise.reject();
},

getPermissions: () => {
return Promise.resolve(UserService.isAdmin());
},
checkError: (error) => {
if (error.status === 401) {
return Promise.reject();
}
return Promise.resolve();
},
getUserIdentity: () =>
Promise.resolve({
name: UserService.getName(),
avatar: UserService.getPicture(),
}),
};
(if you know how to fold code blocks on discord let me know) And here is UserService.ts
/* Source and author: https://github.com/dasniko/keycloak-reactjs-demo/blob/main/src/services/UserService.js */
import Keycloak from 'keycloak-js';

const _kc = new Keycloak({
clientId: import.meta.env.VITE_CLIENT_ID,
url: import.meta.env.VITE_AUTH_URL,
realm: import.meta.env.VITE_REALM,
});

/**
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated.
*/
const initKeycloak = (onAuthenticatedCallback: () => any) => {
_kc
.init({
onLoad: 'check-sso',
checkLoginIframe: false,
})
.then((authenticated) => {
if (!authenticated) {
console.debug('User is not authenticated.');
}
onAuthenticatedCallback();
})
.catch(console.error);
};

const doLogin = () => {
const loginUrl = _kc.createLoginUrl();
location.assign(loginUrl);
};

const doLogout = () => {
const logoutUrl = _kc.createLogoutUrl();
location.assign(logoutUrl);
};

const getToken = () => _kc.token;

const isLoggedIn = () => !!getToken();

const updateTokenOrLogin = async () => {
try {
await _kc.updateToken(5);
} catch (error) {
doLogin();
}
};

const getUsername = () => _kc.tokenParsed?.preferred_username;
const getName = () => _kc.tokenParsed?.name;
const getPicture = () => _kc.tokenParsed?.picture;

const isAdmin = () => _kc.hasResourceRole('admin');

const UserService = {
initKeycloak,
doLogin,
doLogout,
isLoggedIn,
isAdmin,
getToken,
updateTokenOrLogin,
getUsername,
getName,
getPicture,
};

export default UserService;
/* Source and author: https://github.com/dasniko/keycloak-reactjs-demo/blob/main/src/services/UserService.js */
import Keycloak from 'keycloak-js';

const _kc = new Keycloak({
clientId: import.meta.env.VITE_CLIENT_ID,
url: import.meta.env.VITE_AUTH_URL,
realm: import.meta.env.VITE_REALM,
});

/**
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated.
*/
const initKeycloak = (onAuthenticatedCallback: () => any) => {
_kc
.init({
onLoad: 'check-sso',
checkLoginIframe: false,
})
.then((authenticated) => {
if (!authenticated) {
console.debug('User is not authenticated.');
}
onAuthenticatedCallback();
})
.catch(console.error);
};

const doLogin = () => {
const loginUrl = _kc.createLoginUrl();
location.assign(loginUrl);
};

const doLogout = () => {
const logoutUrl = _kc.createLogoutUrl();
location.assign(logoutUrl);
};

const getToken = () => _kc.token;

const isLoggedIn = () => !!getToken();

const updateTokenOrLogin = async () => {
try {
await _kc.updateToken(5);
} catch (error) {
doLogin();
}
};

const getUsername = () => _kc.tokenParsed?.preferred_username;
const getName = () => _kc.tokenParsed?.name;
const getPicture = () => _kc.tokenParsed?.picture;

const isAdmin = () => _kc.hasResourceRole('admin');

const UserService = {
initKeycloak,
doLogin,
doLogout,
isLoggedIn,
isAdmin,
getToken,
updateTokenOrLogin,
getUsername,
getName,
getPicture,
};

export default UserService;
Omer
Omerβ€’17mo ago
My guess is that Keycloak is running as async so when run checkAuth it returns Promise.reject for first load. To avoid a similar situation in our Auth0 example, we wait for Auth0 to be init, https://github.com/refinedev/refine/blob/next/examples/auth-auth0/src/App.tsx#L23 Hey @_rassie , I hope you are very well. Maybe you can help us with this πŸ™‚
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@Omer I can log and check)
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@Omer I did it like this. The logs are the ones that ran only after redirect back from keycloak
No description
No description
Omer
Omerβ€’17mo ago
How can we reproduce this issue?
Omer
Omerβ€’17mo ago
Auth0 Login | refine
Auth0 is a flexible, drop-in solution for adding authentication and authorization services to your applications. Your team and organization can avoid the cost, time, and risk that comes with building your own solution to authenticate and authorize users. You can check the Auth0 document for details.
Google Auth | refine
You can use Google Login to control access and provide identity for your app. This example will guide you through how to connect Google Login into your project using refine.
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Hm, I guess I'll have to create a stackblitz for that, that should not be anyhow related to keycloak...
Omer
Omerβ€’17mo ago
Hey @dontpanicaim , I hope you are very well. Maybe you can help us with this πŸ™‚
conscious-sapphire
conscious-sapphireβ€’17mo ago
does the 404 go away with a manual page reload?
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@dontpanicaim hi, no it doesn't
conscious-sapphire
conscious-sapphireβ€’17mo ago
@Omer thanks! im doing well! hope you are being awesome as normally too πŸ™‚
Omer
Omerβ€’17mo ago
Glad to see you! Are you planning to join the hackathon? ⚑️
conscious-sapphire
conscious-sapphireβ€’17mo ago
ooh is there a hackathon? @metammodern hmm everything looks pretty good, do you know if getUserIdentity is being called? I've been thinking of replace supabase with Hasura in the project I was working on
Omer
Omerβ€’17mo ago
The refine Open Source Hackathon | refine
refine Hackathon is an excellent opportunity for developers to showcase their skills, learn refine and win prizes!
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
let me check
conscious-sapphire
conscious-sapphireβ€’17mo ago
and want to play with the new Inferencer
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@dontpanicaim yes it's called. And I see my user details in top right corner
No description
No description
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@Omer thanks for the links, I used them as a reference when I was implementing this authProvider congrats on transfering examples to codesandbox btw)
conscious-sapphire
conscious-sapphireβ€’17mo ago
it feels like an async issue, like @Omer suggested
Omer
Omerβ€’17mo ago
I started to see a lot of people using Hasura. They seem to be doing well πŸš€ inferencer doesn't support GraphQL for now but @aliemirs will take care
conscious-sapphire
conscious-sapphireβ€’17mo ago
awesome, looking forwarding in trying it out once it supported
Omer
Omerβ€’17mo ago
I wish we had a keycloak example. Unfortunately, I never used it
conscious-sapphire
conscious-sapphireβ€’17mo ago
@metammodern you can try making checkAuth async I work a lot with keycloak, but didnt needed to make an auth provider since it comes with supabase @metammodern ideally all methods in the auth provider are async
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
mhm, I'll try, but for @Omer here's a tip/hint: you told that in Auth0 you had to add early loading return if auth0 was loading. In my case I don't need it. Check the UserService initKeycloak method, it accepts a callback. And that callback is app rendering in index.tsx file:
const container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);

const renderApp = () => {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
};

UserService.initKeycloak(renderApp);
const container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);

const renderApp = () => {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
};

UserService.initKeycloak(renderApp);
So I don't render anything until keycloak is initialized and ready to provide any reasonable info
Omer
Omerβ€’17mo ago
Yes, you are right
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@dontpanicaim it didn't help. I am suspicios that something is in catchAll part I see in example a prewritten antD ErrorComponent is used. Can you give me link to it? I'll check how it's written
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
GitHub
refine/index.tsx at next Β· refinedev/refine
Build your React-based CRUD applications, without constraints. - refine/index.tsx at next Β· refinedev/refine
Omer
Omerβ€’17mo ago
GitHub
refine/index.tsx at next Β· refinedev/refine
Build your React-based CRUD applications, without constraints. - refine/index.tsx at next Β· refinedev/refine
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
yes swizzle, I was thinking about "swoosh"πŸ˜‚ wait what am I doing. Let me just remove catchAll and see what happens. Dumb... nah, not the rootcause(
Omer
Omerβ€’17mo ago
The problem is not with the error page. The problem is somehow checkAuth returns promise.reject after login I guess the easiest way to solve the problem is to look at Auth0 and Google Auth examples
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
So if I hardcode to return Promise.resolve() it will work you say?)
Omer
Omerβ€’17mo ago
Yes
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Just checked, nope) didn't work)
Omer
Omerβ€’17mo ago
Is it possible for you to share a video where we can see the issue?
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Of course!
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
The only thing that I'm also suspicious is that hash in the end that's being added by keycloak. But it's being removed immideately(I gues by refine or by react-router)
Omer
Omerβ€’17mo ago
Aha okay If you always return promise.resolve() on the checkAuth the login screen will not appear. You can only see it in the unauthenticated state
Omer
Omerβ€’17mo ago
Auth Provider | refine
refine let's you set authentication logic by providing the authProvider property to the `` component.
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
I did a trick: I logged out, then went to sign in page of keycloak and during that I changed the code. When keycloak redirected back to app it already had "checkAuth" always returning Promise.resolve(). Does this count?) I'll get back to this tomorrow
Omer
Omerβ€’17mo ago
I will create a Keycloak example for you tomorrow ⚑️
foreign-sapphire
foreign-sapphireβ€’17mo ago
Oh, seems I'm late to the party πŸ™‚ I'm afraid I can't contribute much because it's been some time since I last used keycloak.js (I'm firmly in the oauth2-proxy camp) and I also don't see an obvious error in the code. I'll follow the discussion though, maybe something will catch my eye.
Omer
Omerβ€’17mo ago
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
I wanted to check it but it's not in sandbox yet. I'll try locally though
No description
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
@Omer aha! Got you!)
No description
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
So what I did: I logged out completely. Then Went to "localhost:3000/posts", it changed to "http://localhost:3000/login?to=%2Fposts" and opened a login Page. So far so good. Then I logged in, it redirected me back and showed me 404(screenshot above)
Omer
Omerβ€’17mo ago
GitHub
refine/App.tsx at master Β· refinedev/refine
Build your React-based CRUD applications, without constraints. - refine/App.tsx at master Β· refinedev/refine
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
yep, i see. but it's logout, not login and even if it was login--it's the default
Omer
Omerβ€’17mo ago
How i can reproduce?
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
I wrote instruction above but let me record a video will be clearer
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Omer
Omerβ€’17mo ago
Aha I got it πŸ™‚
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
awesome) And thank you for keycloak example, I was thinking about adding it myself since you have keycloak in providers diagram on npm) Now we have source to refer to in our internal docs)
Omer
Omerβ€’17mo ago
I have a temporary solution to this,
const authProvider: AuthProvider = {
login: async () => {
const urlSearchParams = new URLSearchParams(window.location.search);
const { to } = Object.fromEntries(urlSearchParams.entries());
await keycloak.login({
redirectUri: to ? `${window.location.origin}${to}` : undefined,
});
return Promise.resolve(false);
},
const authProvider: AuthProvider = {
login: async () => {
const urlSearchParams = new URLSearchParams(window.location.search);
const { to } = Object.fromEntries(urlSearchParams.entries());
await keycloak.login({
redirectUri: to ? `${window.location.origin}${to}` : undefined,
});
return Promise.resolve(false);
},
xenophobic-harlequin
xenophobic-harlequinβ€’17mo ago
Looks good enough, I'll apply it) Should I maybe create an issue so that core team digs into rootcauses of this?
Omer
Omerβ€’17mo ago
It would be great πŸ₯
xenophobic-harlequin
xenophobic-harlequinβ€’14mo ago
Ok, I'll get back to this in a while when I apply this fix locally. Thank you so much @Omer ! Honestly did not expect much of involvment from a CTO/CEO but seems you really like being activeπŸ‘ 😊 Hi, @Omer ! The issue is now resolved with your fix. When logging in I don't have a 404 anymore. However, if I implicitely go to "http://localhost:5173/login?to=%2Ffeature_content" the issue still remains, I still see 404. Probably an issue with router at this point and not with keycloak or smth. What do you think? πŸ₯Ί
flat-fuchsia
flat-fuchsiaβ€’14mo ago
Hey @metammodern can you share the code for the related routes, i think we can resolve this with some quick tweaks
xenophobic-harlequin
xenophobic-harlequinβ€’14mo ago
@aliemirs hello:) I use the default router provider: import routerProvider from '@pankod/refine-react-router-v6'; in App.tsx And then I created resources according to docs. and also I have auth provider matching the docs examples
flat-fuchsia
flat-fuchsiaβ€’14mo ago
Can you share the /login route and its wrapper route?
xenophobic-harlequin
xenophobic-harlequinβ€’14mo ago
And after that I open then link provided above. First it requests authorization, I authorize(sign in), it comes back and resource list page opens(Again, this started working after Omer's fix applied). But if I open the same link (login?to=/resource_name) it gives 404. So the issue is not in authProvider Umm, let me see if I even have such thing I have a pages/login.tsx file. Is that what you want?
import { useLogin } from '@pankod/refine-core';
import { Button } from '@pankod/refine-mui';

export const Login: React.FC = () => {
const { mutate: login } = useLogin();

return (
<div
style={{
background: `radial-gradient(50% 50% at 50% 50%, #63386A 0%, #310438 100%)`,
backgroundSize: 'cover',
}}
>
<div style={{ height: '100vh', display: 'flex' }}>
<div
style={{
maxWidth: '200px',
margin: 'auto',
display: 'flex',
flexDirection: 'column',
}}
>
<div style={{ marginBottom: '28px' }}>
<img src="/images/company_namepix_white.svg" alt="company_namepix Admin Panel" />
</div>
<Button size="large" onClick={login} variant="contained">
Sign In with company_name
</Button>
</div>
</div>
</div>
);
};
import { useLogin } from '@pankod/refine-core';
import { Button } from '@pankod/refine-mui';

export const Login: React.FC = () => {
const { mutate: login } = useLogin();

return (
<div
style={{
background: `radial-gradient(50% 50% at 50% 50%, #63386A 0%, #310438 100%)`,
backgroundSize: 'cover',
}}
>
<div style={{ height: '100vh', display: 'flex' }}>
<div
style={{
maxWidth: '200px',
margin: 'auto',
display: 'flex',
flexDirection: 'column',
}}
>
<div style={{ marginBottom: '28px' }}>
<img src="/images/company_namepix_white.svg" alt="company_namepix Admin Panel" />
</div>
<Button size="large" onClick={login} variant="contained">
Sign In with company_name
</Button>
</div>
</div>
</div>
);
};
Other than that, I don't have any explicit "/login" route defined anywhere If you want(and if you can) we may jump on a call I can screenshare then and show exact issue. If we resovle it this way I will write the solution here so it won't be lost