I want to add OIDC to my React application and I am using oidc-client-ts since it seems popular and is still being maintained. My problem is that I miss some React examples.
What I want is all but one routes to be protected. If the user is not authenticated, they should be redirected to the login screen which has a button to activate the auth-flow using a custom provider.
I have tried to use these two examples, but I am unsure how to glue them together and how to convert the Angular code to React.
So far I have wrapped the entire application in an AuthContext and made all but one route private as in the first example:
index.tsx:
<StrictMode>
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path={routes.LOGIN} element={<LoginContainer />} />
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="/openid/callback" element={<AuthCallback />} />
// Other pages
</Route>
<Route path="*" element={<ErrorPage />} />
</Routes>
</BrowserRouter>
</AuthProvider>
</StrictMode>
The Layout
-component with a private route, to make all paths but "/login"
private:
function RequireAuth({ children }: { children: JSX.Element }) {
const auth = useAuth();
if (!auth.user) {
return <Navigate to="/login" replace />;
}
return children;
}
function Layout() {
return (
<RequireAuth>
<>
<Header />
<Main />
<Footer />
</>
</RequireAuth>
);
}
AuthProvider:
const AuthContext = createContext<AuthContextType>(null!);
const useAuth = () => useContext(AuthContext);
function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<any>(null);
const authService = new AuthService();
const login = () => authService.login().then(user1 => setUser(user1));
const loginCallback = async () => {
const authedUser = await authService.loginCallback();
setUser(authedUser);
};
const logout = () => authService.login().then(() => setUser(null));
const value = { user, login, logout };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export { AuthProvider, useAuth };
The authService
is just copied from the angular example:
import { User, UserManager } from "oidc-client-ts";
export default class AuthService {
userManager: UserManager;
constructor() {
const settings = {
authority: "...",
client_id: "...",
redirect_uri: "http://localhost:3000/openid/callback",
client_secret: "...",
post_logout_redirect_uri: "http://localhost:3000/login"
};
this.userManager = new UserManager(settings);
}
public getUser(): Promise<User | null> {
return this.userManager.getUser();
}
public login(): Promise<void> {
return this.userManager.signinRedirect();
}
public loginCallback(): Promise<User> {
return this.userManager.signinRedirectCallback();
}
public logout(): Promise<void> {
return this.userManager.signoutRedirect();
}
}
My issue is that I do not know how to set the user in the AuthProvider
so I can check if I am auth'ed in the RequireAuth
-component. It is not set in my then
in the AuthProvider
s login and logout functions, so I just get redirected to the login-page whenever I try to login.
Can someone tell me how I can make the authentication flow using OIDC and restrict all my paths but one to authenticated users only?
Furthermore this answer says that there should be an AuthorizationCallback
-component to parse the URL. When I use oidc-client-ts which seems to parse the data for me, do I really need this extra step or can I just have the redirect URL be "/" or "/home"?
Edit:
I found out that signinRedirect
goes to a new URL which means that the rest of the script is never run. signinRedirectCallback
is the call that returns the user. I will post it as an answer when I have figured out how to protect the routes properly. The check in RequireAuth
is done before the user is set. How do I postpone the check until the user has been set so I do not redirect to login even though I am signed in? And if I refresh the page I lose the user
state from AuthProvider
and I will be sent to the login page even though there is an active session. I am unsure where and how I check if I have a session running when I load the app in a clean way.