Repository: honojs/hono
File: deno_dist/utils/jwt/jwt.ts (TypeScript)

Code before:
  JwtTokenExpired,
  JwtTokenSignatureMismatched,
  JwtTokenIssuedAt,
} from './types.ts'
import { utf8Decoder, utf8Encoder } from './utf8.ts'

const encodeJwtPart = (part: unknown): string =>
  encodeBase64Url(utf8Encoder.encode(JSON.stringify(part))).replace(/=/g, '')
const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '')

const decodeJwtPart = (part: string): unknown =>
  JSON.parse(utf8Decoder.decode(decodeBase64Url(part)))

export interface TokenHeader {
  alg: SignatureAlgorithm
  typ: 'JWT'
}

// eslint-disable-next-line
export function isTokenHeader(obj: any): obj is TokenHeader {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'alg' in obj &&
    Object.values(AlgorithmTypes).includes(obj.alg) &&
    'typ' in obj &&
    obj.typ === 'JWT'
  )
}

export const sign = async (
  payload: JWTPayload,
  privateKey: SignatureKey,
  alg: SignatureAlgorithm = 'HS256'
): Promise<string> => {
  const encodedPayload = encodeJwtPart(payload)
  const encodedHeader = encodeJwtPart({ alg, typ: 'JWT' } satisfies TokenHeader)

  const partialToken = `${encodedHeader}.${encodedPayload}`

  const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken))
  const signature = encodeSignaturePart(signaturePart)

  return `${partialToken}.${signature}`
}

export const verify = async (
  token: string,
  publicKey: SignatureKey,
  alg: SignatureAlgorithm = 'HS256'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any

Review comment (security):
typ is case-insensitive, but left uppercase because uppercase is recommended for compatibility.

> While media type names are not case sensitive, it is RECOMMENDED that "JWT" always be spelled using uppercase characters for compatibility with legacy implementations.


Code after:
  JwtTokenExpired,
  JwtTokenSignatureMismatched,
  JwtTokenIssuedAt,
} from './types.ts'
import { utf8Decoder, utf8Encoder } from './utf8.ts'

const encodeJwtPart = (part: unknown): string =>
  encodeBase64Url(utf8Encoder.encode(JSON.stringify(part))).replace(/=/g, '')
const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '')

const decodeJwtPart = (part: string): unknown =>
  JSON.parse(utf8Decoder.decode(decodeBase64Url(part)))

export interface TokenHeader {
  alg: SignatureAlgorithm
  typ?: 'JWT'
}

// eslint-disable-next-line
export function isTokenHeader(obj: any): obj is TokenHeader {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'alg' in obj &&
    Object.values(AlgorithmTypes).includes(obj.alg) &&
    (!('typ' in obj) || obj.typ === 'JWT')
  )
}

export const sign = async (
  payload: JWTPayload,
  privateKey: SignatureKey,
  alg: SignatureAlgorithm = 'HS256'
): Promise<string> => {
  const encodedPayload = encodeJwtPart(payload)
  const encodedHeader = encodeJwtPart({ alg, typ: 'JWT' } satisfies TokenHeader)

  const partialToken = `${encodedHeader}.${encodedPayload}`

  const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken))
  const signature = encodeSignaturePart(signaturePart)

  return `${partialToken}.${signature}`
}

export const verify = async (
  token: string,
  publicKey: SignatureKey,
  alg: SignatureAlgorithm = 'HS256'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> => {