FusionAuth
    • Home
    • Categories
    • Recent
    • Popular
    • Pricing
    • Contact us
    • Docs
    • Login
    1. Home
    2. laurahernandez
    L
    • Profile
    • Following 0
    • Followers 0
    • Topics 2
    • Posts 5
    • Best 0
    • Controversial 0
    • Groups 0

    laurahernandez

    @laurahernandez

    0
    Reputation
    1
    Profile views
    5
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    laurahernandez Unfollow Follow

    Latest posts made by laurahernandez

    • RE: nextAuth SignOut and revoking app sessions

      @mark-robustelli Yes, it's all working. After the logout flow executes, the sessions that were being left behind on FusionAuth are now being revoked properly. Thanks.

      posted in Q&A
      L
      laurahernandez
    • RE: Customize the login screen

      @mark-robustelli I’m aiming for a modern, seamless passwordless login experience (with some IdP buttons), but the default theme doesn't have a quick way to hide password fields, and the flow could be improved. I don't think leaving the possibility of an open hole is a solution. Are there plans for a dedicated passwordless mode and theme? One in which passwords are not expected and the server could reject passwords when passwordless is enabled.

      I’ll try customizing a theme next week, but I’m concerned about the complexity. I didn’t expect this during evaluation of FusionAuth, assuming your UI would match Clerk or Stytch component approach. Do you have any planned upcoming UI enhancements?

      Thanks for the guidance!
      P.S. I saw on "Preview" mode of a theme, I can choose to "Hide Password Field" (or something like that) - is that available during design mode? If it is, I couldn't find it.

      posted in General Discussion
      L
      laurahernandez
    • RE: nextAuth SignOut and revoking app sessions

      @mark-robustelli I have it set to "All applications".

      I got this working, but please let me know if there is a better way.

      Basically the flow is: hit the sign out button, this navigates to api/auth/global-logout which reads the refreshTokenId that had been stored in the nextAuth token (because of the jwt callback defined in authOptions), and uses that to revoke that session for that user. Then the fusionAuth OAuth logout URL (oauth2/logout) is called which revokes the FusionAuth SSO session and then that calls the tenant Logout URL which is the /logout page. Then the logout page calls nextAuth's signOut() to destroy the nextAuth cookies on the client and redirect to /login.

      This GitHub conversation helped me get this done: https://github.com/nextauthjs/next-auth/discussions/3938#discussioncomment-2165150
      Reading the whole dialog before that post really helped me understand what was going on.
      Then this FusionAuth example shows how to revoke refresh tokens: https://github.com/FusionAuth/fusionauth-example-node-centralized-sessions/blob/main/changebank/src/index.ts
      (the premise of the setup: https://github.com/FusionAuth/fusionauth-example-node-centralized-sessions)
      And of course the background information as to why we have to do all this: https://fusionauth.io/docs/lifecycle/authenticate-users/logout-session-management

      I'm still working on it, but it does work already, and the code looks like this:
      For the nextAuth setup:

      api/lib/authOptions.ts

      import FusionAuthProvider from 'next-auth/providers/fusionauth';
      const fusionAuthIssuer = process.env.FUSIONAUTH_ISSUER;
      export const fusionAuthClientId = process.env.FUSIONAUTH_CLIENT_ID;
      const fusionAuthClientSecret = process.env.FUSIONAUTH_CLIENT_SECRET;
      export const fusionAuthUrl = process.env.FUSIONAUTH_URL;
      export const fusionAuthTenantId = process.env.FUSIONAUTH_TENANT_ID;
      export const fusionAuthRefreshTokenScopedApiKey =
        process.env.FUSION_AUTH_REFRESHTOKEN_API_KEY;
      
      const missingError = 'missing in environment variables.';
      if (!fusionAuthIssuer) {
        throw Error('FUSIONAUTH_ISSUER' + missingError);
      }
      if (!fusionAuthClientId) {
        throw Error('FUSIONAUTH_CLIENT_ID' + missingError);
      }
      if (!fusionAuthClientSecret) {
        throw Error('FUSIONAUTH_CLIENT_SECRET' + missingError);
      }
      if (!fusionAuthUrl) {
        throw Error('FUSIONAUTH_URL' + missingError);
      }
      if (!fusionAuthTenantId) {
        throw Error('FUSIONAUTH_TENANT_ID' + missingError);
      }
      if (!fusionAuthRefreshTokenScopedApiKey) {
        throw Error('FUSION_AUTH_REFRESHTOKEN_API_KEY' + missingError);
      }
      export const authOptions = {
        providers: [
          FusionAuthProvider({
            issuer: fusionAuthIssuer,
            clientId: fusionAuthClientId,
            clientSecret: fusionAuthClientSecret,
            wellKnown: `${fusionAuthUrl}/.well-known/openid-configuration/${fusionAuthTenantId}`,
            tenantId: fusionAuthTenantId, // Only required if you're using multi-tenancy
            authorization: {
              params: {
                scope: 'openid offline_access email profile',
              },
            },
          }),
        ],
        callbacks: {
          async session({ session, token }: any) {
            session.user = token.user;
            return session;
          },
          async jwt({ token, account, user }: any) {
            // console.log({ token, account, user });
            if (user) {
              token.user = user;
            }
            // Persist the OAuth access_token to the token right after signin to use in token revocation
            if (account) {
              // token.accessToken = account.access_token;
              token.refreshTokenId = account.refresh_token_id;
            }
            return token;
          },
        },
        pages: {
          signIn: '/login', // Custom login page
        },
        debug: true,
      };
      

      And at api\auth[...nextauth]\route.ts

      import NextAuth from 'next-auth';
      import { authOptions } from '../lib/authOptions';
      
      const handler = NextAuth(authOptions);
      
      export { handler as GET, handler as POST };
      

      Then in your FusionAuth setup:
      Configure the tenant "logout URL" (under OAuth tab) to http://localhost:3000/logout (I did it at the tenant level, but it can also be specified per application), and define that page in your Next app:

      /logout.ts:

      'use client';
      import { signOut } from 'next-auth/react';
      import { useRouter } from 'next/navigation';
      import { useEffect } from 'react';
      
      export default function LogoutPage() {
        const router = useRouter();
        useEffect(() => {
          async function signout() {
            await signOut({ redirect: false });
            router.push('/login');
            // ...
          }
          signout();
        }, []);
        return (
          <>
            <div>Please wait while you are being logged out</div>
          </>
        );
      }
      

      The sign out button on your app:

      {session && (
                  <button
                    onClick={async () => {
                      window.location.href = '/api/auth/global-logout';
                    }}
                    type='button'
                  >
                    Sign Out
                  </button>
                )}
      

      Your api/auth/global-logout route

      import { NextRequest, NextResponse } from 'next/server';
      import jwt from 'next-auth/jwt';
      import FusionAuthClient from '@fusionauth/typescript-client';
      import {
        fusionAuthClientId,
        fusionAuthRefreshTokenScopedApiKey,
        fusionAuthTenantId,
        fusionAuthUrl,
      } from '../lib/authOptions';
      
      export async function GET(req: NextRequest) {
        try {
          const token = await jwt.getToken({
            req,
            secret: process.env.NEXTAUTH_SECRET,
          });
          console.debug('token', token);
          if (!token) {
            console.warn('No JWT token found when calling /global-logout endpoint');
            return NextResponse.redirect(process.env.NEXTAUTH_URL!);
          }
          console.log('token?.user', token?.user);
      
          if (!token.idToken)
            console.warn(
              "Without an id_token the user won't be redirected back from the IdP after logout.",
            );
      
          const fusionAuthLogoutUrl = getLogoutRedirectUrl(req, token.idToken);
      
          //revoke this token ID. Should I revoke more?
          const refreshTokenId = token?.refreshTokenId;
          if (refreshTokenId && typeof refreshTokenId === 'string') {
            if (fusionAuthRefreshTokenScopedApiKey && fusionAuthUrl) {
              console.debug('revoking refresh token with ID: ', refreshTokenId);
              const client = new FusionAuthClient(
                fusionAuthRefreshTokenScopedApiKey,
                fusionAuthUrl,
                fusionAuthTenantId,
              );
              const revokeResponse = await client.revokeRefreshTokenById(
                refreshTokenId,
              );
              console.debug(revokeResponse);
            }
          }
      
          // revoke ALL active user sessions regardless of device, application, etc
          // const user = token?.user;
          // if (user && typeof user === 'object' && 'id' in user) {
          //   console.log('got user info: ', user);
          //   const userId = (user as { id: string; email: string }).id;
      
          //   if (userId && fusionAuthRefreshTokenScopedApiKey && fusionAuthUrl) {
          //     console.log('revoking all user refreshTokens (sessions)');
          //     const client = new FusionAuthClient(
          //       fusionAuthRefreshTokenScopedApiKey,
          //       fusionAuthUrl,
          //       fusionAuthTenantId,
          //     );
          //     console.log('revoking ');
          //     const revokingResponse = await client.revokeRefreshTokensByUserId(
          //       userId,
          //     );
          //     console.log(revokingResponse.wasSuccessful());
          //     console.log(revokingResponse.exception);
          //   }
          // }
          // to initiate the process of ending FusionAuth's SSO session and follow the configured logout URLs for the application. See https://github.com/FusionAuth/fusionauth-example-node-centralized-sessions/blob/main/changebank/src/index.ts
          return NextResponse.redirect(fusionAuthLogoutUrl);
        } catch (error) {
          console.error(error);
          console.error(JSON.stringify(error));
          return NextResponse.redirect(process.env.NEXTAUTH_URL!);
        }
      }
      
      function getLogoutRedirectUrl(req: NextRequest, idToken: any): string {
        const { searchParams } = new URL(req.url);
        const postLogoutRedirectUri = searchParams.get('post_logout_redirect_uri');
      
        const params = new URLSearchParams();
      
        if (postLogoutRedirectUri) {
          params.set('post_logout_redirect_uri', postLogoutRedirectUri);
        }
      
        if (fusionAuthTenantId) {
          params.set('tenantId', fusionAuthTenantId);
        }
      
        if (fusionAuthClientId) {
          params.set('client_id', fusionAuthClientId);
        } else if (idToken) {
          params.set('id_token_hint', idToken);
        }
      
        return `${fusionAuthUrl}/oauth2/logout?${params.toString()}`;
      }
      

      There is also commented out code that shows how to revoke all sessions like in the FusionAuth example, but I guess that would revoke sessions that are from another device as well which I'd rather not do.

      posted in Q&A
      L
      laurahernandez
    • nextAuth SignOut and revoking app sessions

      I'm trying to use FusionAuth with nextAuth, and when I call SignOut() from my app, the nextAuth token is destroyed, and then I have it redirect to my api/logout route where I call FusionAuth's oauth2/logout url which destroys the FusionAuth SSO session; however, I can't destroy the application sessions that I still see for that user via the admin console. I have a logout url configured for the tenant which calls my api/endSession route, but I was hoping to get the userId from the nextAuth token here to destroy all refreshTokens; however, at this point, the nextAuth token is gone.

      What is the correct way to sign out of all sessions when using nextAuth?

      posted in Q&A
      L
      laurahernandez
    • Customize the login screen

      Hi!

      I think what I'm trying to do should be very easy, but I can't figure out how to do it. I want logins to my tenant apps to allow for passwordless login, or Login with Google, or Login with Microsoft, and that's it, no passwords, and no signups allowed (user has to exist already to be able to sign in).

      But even though I've turned on Passwordless and only the Google IdP for now, the login screen still shows everything for email/password. Then I tried to make a Simple theme, but it didn't allow me to turn the password related things off. Then I tried to make a template (which I'd rather not), and I see that I could take the password related elements off, but I have no access to manipulate the passwordless option or the IdP buttons; is that right?

      Let's say I want this dialog from your own homepage sample screenshots, how do I achieve this exact login layout?
      https://cdn.prod.website-files.com/664cfafd1b780dd90b9bc416/66cc9e6ab66b7479fd4ead34_enterprise-secure-authentication-image 2.png
      OR
      https://cdn.prod.website-files.com/664cfafd1b780dd90b9bc416/66ea66292c02554dc34ce752_FA Hero Animation - Loop.svg

      Thanks,
      Laura

      posted in General Discussion
      L
      laurahernandez