title: Adding OIDC to an React application with restricted routes
body: <p>I want to add OIDC to my React application and I am using <a href="https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx" rel="nofollow noreferrer">oidc-client-ts</a> since it seems popular and is still being maintained. My problem is that I miss some React examples.</p>
  <p>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.</p>
  <p>I have tried to use <a href="https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx" rel="nofollow noreferrer">these</a> <a href="https://github.com/authts/sample-angular-oidc-client-ts" rel="nofollow noreferrer">two</a> examples, but I am unsure how to glue them together and how to convert the Angular code to React.</p>
  <p>So far I have wrapped the entire application in an AuthContext and made all but one route private as in the <a href="https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx" rel="nofollow noreferrer">first example</a>:</p>
  <p><strong>index.tsx:</strong></p>
  <pre><code>&lt;StrictMode&gt;
      &lt;AuthProvider&gt;
          &lt;BrowserRouter&gt;
              &lt;Routes&gt;
                  &lt;Route path={routes.LOGIN} element={&lt;LoginContainer /&gt;} /&gt;
                  &lt;Route element={&lt;Layout /&gt;}&gt;
                      &lt;Route index element={&lt;Home /&gt;} /&gt;
                      &lt;Route path=&quot;/openid/callback&quot; element={&lt;AuthCallback /&gt;} /&gt;
                          // Other pages
                  &lt;/Route&gt;
                  &lt;Route path=&quot;*&quot; element={&lt;ErrorPage /&gt;} /&gt;
              &lt;/Routes&gt;
          &lt;/BrowserRouter&gt;
      &lt;/AuthProvider&gt;
  &lt;/StrictMode&gt;
  </code></pre>
  <p>The <code>Layout</code>-component with a private route, to make all paths but <code>&quot;/login&quot;</code> private:</p>
  <pre><code>function RequireAuth({ children }: { children: JSX.Element }) {
      const auth = useAuth();
  
      if (!auth.user) {
          return &lt;Navigate to=&quot;/login&quot; replace /&gt;;
      }
  
      return children;
  }
  
  function Layout() {
      return (
          &lt;RequireAuth&gt;
              &lt;&gt;
                  &lt;Header /&gt;
                  &lt;Main /&gt;
                  &lt;Footer /&gt;
              &lt;/&gt;
          &lt;/RequireAuth&gt;
      );
  }
  </code></pre>
  <p><strong>AuthProvider:</strong></p>
  <pre><code>const AuthContext = createContext&lt;AuthContextType&gt;(null!);
  
  const useAuth = () =&gt; useContext(AuthContext);
  
  function AuthProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState&lt;any&gt;(null);
      const authService = new AuthService();
  
      const login = () =&gt; authService.login().then(user1 =&gt; setUser(user1));
  
      const loginCallback = async () =&gt; {
          const authedUser = await authService.loginCallback();
          setUser(authedUser);
      };
  
      const logout = () =&gt; authService.login().then(() =&gt; setUser(null));
      const value = { user, login, logout };
  
      return &lt;AuthContext.Provider value={value}&gt;{children}&lt;/AuthContext.Provider&gt;;
  }
  
  export { AuthProvider, useAuth };
  </code></pre>
  <p>The <code>authService</code> is just copied from the <a href="https://github.com/authts/sample-angular-oidc-client-ts/blob/main/src/app/core/services/auth.service.ts" rel="nofollow noreferrer">angular example</a>:</p>
  <pre><code>import { User, UserManager } from &quot;oidc-client-ts&quot;;
  
  export default class AuthService {
      userManager: UserManager;
  
      constructor() {
          const settings = {
              authority: &quot;...&quot;,
              client_id: &quot;...&quot;,
              redirect_uri: &quot;http://localhost:3000/openid/callback&quot;,
              client_secret: &quot;...&quot;,
              post_logout_redirect_uri: &quot;http://localhost:3000/login&quot;
          };
          this.userManager = new UserManager(settings);
      }
  
      public getUser(): Promise&lt;User | null&gt; {
          return this.userManager.getUser();
      }
  
      public login(): Promise&lt;void&gt; {
          return this.userManager.signinRedirect();
      }
  
      public loginCallback(): Promise&lt;User&gt; {
          return this.userManager.signinRedirectCallback();
      }
  
      public logout(): Promise&lt;void&gt; {
          return this.userManager.signoutRedirect();
      }
  }
  </code></pre>
  <p>My issue is that I do not know how to set the user in the <code>AuthProvider</code> so I can check if I am auth'ed in the <code>RequireAuth</code>-component. It is not set in my <code>then</code> in the <code>AuthProvider</code>s login and logout functions, so I just get redirected to the login-page whenever I try to login.</p>
  <p>Can someone tell me how I can make the authentication flow using OIDC and restrict all my paths but one to authenticated users only?</p>
  <p>Furthermore <a href="https://stackoverflow.com/a/59370777/7925759">this answer</a> says that there should be an <code>AuthorizationCallback</code>-component to parse the URL. When I use <a href="https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx" rel="nofollow noreferrer">oidc-client-ts</a> which seems to parse the data for me, do I really need this extra step or can I just have the redirect URL be &quot;/&quot; or &quot;/home&quot;?</p>
  <p><strong>Edit:</strong></p>
  <p>I found out that <code>signinRedirect</code> goes to a new URL which means that the rest of the script is never run. <code>signinRedirectCallback</code> 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 <code>RequireAuth</code> 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 <code>user</code> state from <code>AuthProvider</code> 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.</p>