useJWT

JSON Web Tokens for client-side token decoding and validation.

Token Format: JWT with Ed25519 signatures for secure authentication

Import

import { useJWT } from '@heliorim/sdk-react';

Type Signature

interface UseJWTReturn {
  decoded: DecodedJWT | null;
  isExpired: boolean;
  expiration: Date | null;
  isValid: boolean;
  decodeToken: (token: string) => DecodedJWT | null;
  isTokenExpired: (token: string) => boolean;
  getTokenExpiration: (token: string) => Date | null;
  getClaim: <T = any>(token: string, claimName: string) => T | null;
  getSessionId: (token: string) => string | null;
  getUserId: (token: string) => string | null;
  getTenantId: (token: string) => string | null;
  isValidToken: (token: string) => boolean;
  clearError: () => void;
}

interface DecodedJWT {
  header: {
    alg: "EdDSA" | "ES256" | "ES384" | "ES512" | "RS256";
    typ: "JWT";
    kid?: string;
  };
  payload: {
    sub: string; // Subject (User ID)
    aud: string | string[]; // Audience
    iss: string; // Issuer
    exp: number; // Expiration
    iat: number; // Issued at
    sid?: string; // Session ID
    tid?: string; // Tenant ID
    [key: string]: any; // Custom claims
  };
}

Basic Usage

Never store auth tokens in localStorage or sessionStorage. Use httpOnly cookies set by the server. Pass tokens to useJWT from server-rendered loaders or context.

// Token from server loader (httpOnly cookie handled by server)
// Never store auth tokens in localStorage - XSS risk
function TokenAuthentication({ tokenFromLoader }: { tokenFromLoader: string | null }) {
  const [token, setToken] = useState<string | null>(tokenFromLoader);
  const {
    decoded,
    isExpired,
    expiration,
    isValid,
    decodeToken,
    isTokenExpired,
    getTokenExpiration,
    getSessionId,
    getUserId
  } = useJWT(token);

  // Token comes from server context/loader, not localStorage
  useEffect(() => {
    if (tokenFromLoader) setToken(tokenFromLoader);
  }, [tokenFromLoader]);

  const validateToken = (tokenString: string) => {
    try {
      const decoded = decodeToken(tokenString);
      if (!decoded) return false;
      if (isTokenExpired(tokenString)) return false;
      if (decoded.payload.aud !== 'https://api.example.com') return false;
      return true;
    } catch {
      return false;
    }
  };

  return (
    <div>
      <h3>Token Status</h3>
      {token && decoded && (
        <div>
          <p>User ID: {getUserId(token)}</p>
          <p>Session ID: {getSessionId(token)}</p>
          <p>Expires: {expiration?.toISOString()}</p>
          <p>Valid: {isValid ? 'Yes' : 'No'}</p>
        </div>
      )}
    </div>
  );
}

Token Validation

Client-Side Decoding

function TokenValidation() {
  const [token, setToken] = useState<string | null>(null);
  const { decoded, isExpired, isValid, decodeToken, isTokenExpired } = useJWT(token);

  const validateToken = (tokenString: string) => {
    try {
      // Decode token (unsafe - no signature verification)
      const decoded = decodeToken(tokenString);
      
      if (!decoded) {
        console.log('Invalid token format');
        return false;
      }

      // Check expiration
      if (isTokenExpired(tokenString)) {
        console.log('Token expired');
        return false;
      }

      // Check required claims
      if (!decoded.payload.sub) {
        console.log('Missing subject claim');
        return false;
      }

      return true;
    } catch (error) {
      console.error(
        'Token validation failed:',
        error instanceof Error ? error.message : "unknown error",
      );
      return false;
    }
  };

  return (
    <div>
      <h3>Token Validation</h3>
      <p>Client-side JWT validation without signature verification</p>
      
      {token && decoded && (
        <div className="token-info">
          <h4>Token Information</h4>
          <p>Valid: {isValid ? 'Yes' : 'No'}</p>
          <p>Expired: {isExpired ? 'Yes' : 'No'}</p>
          <p>User ID: {decoded.payload.sub}</p>
          <p>Audience: {decoded.payload.aud}</p>
          <p>Issuer: {decoded.payload.iss}</p>
        </div>
      )}
    </div>
  );
}

Security Considerations

⚠️ Important

  • Client-side JWT decoding does NOT verify signatures
  • Always verify tokens server-side for security-critical operations
  • Use this hook for UI state and client-side validation only
  • Never trust client-side validation for authorization decisions

✓ Recommended

  • Use for UI state management and user experience
  • Check expiration for token refresh prompts
  • Display user information from token claims
  • Validate token format before sending to server

Related Hooks