Skip to content

Commit

Permalink
feat: add jose.decodeJwt utility
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Feb 7, 2022
1 parent 7835489 commit 3d2a2b8
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 4 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ import * as jose from 'https://deno.land/x/jose/index.ts'

- JSON Web Tokens (JWT)
- [Signing](docs/classes/jwt_sign.SignJWT.md#readme)
- [Verification & Claims Set Validation](docs/functions/jwt_verify.jwtVerify.md#readme)
- [Verification & JWT Claims Set Validation](docs/functions/jwt_verify.jwtVerify.md#readme)
- Encrypted JSON Web Tokens
- [Encryption](docs/classes/jwt_encrypt.EncryptJWT.md#readme)
- [Decryption & Claims Set Validation](docs/functions/jwt_decrypt.jwtDecrypt.md#readme)
- [Decryption & JWT Claims Set Validation](docs/functions/jwt_decrypt.jwtDecrypt.md#readme)
- Key Import
- [JWK Import](docs/functions/key_import.importJWK.md#readme)
- [Public Key Import (SPKI)](docs/functions/key_import.importSPKI.md#readme)
Expand All @@ -72,6 +72,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
- [Public Key Export](docs/functions/key_export.exportSPKI.md#readme)
- Utilities
- [Decoding Token's Protected Header](docs/functions/util_decode_protected_header.decodeProtectedHeader.md#readme)
- [Decoding JWT Claims Set](docs/functions/util_decode_jwt.decodeJwt.md#readme)
- [Unsecured JWT](docs/classes/jwt_unsecured.UnsecuredJWT.md#readme)
- [JOSE Errors](docs/modules/util_errors.md#readme)

Expand Down
5 changes: 3 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import * as jose from 'https://deno.land/x/jose/index.ts'

- JSON Web Tokens (JWT)
- [Signing](classes/jwt_sign.SignJWT.md#readme)
- [Verification & Claims Set Validation](functions/jwt_verify.jwtVerify.md#readme)
- [Verification & JWT Claims Set Validation](functions/jwt_verify.jwtVerify.md#readme)
- Encrypted JSON Web Tokens
- [Encryption](classes/jwt_encrypt.EncryptJWT.md#readme)
- [Decryption & Claims Set Validation](functions/jwt_decrypt.jwtDecrypt.md#readme)
- [Decryption & JWT Claims Set Validation](functions/jwt_decrypt.jwtDecrypt.md#readme)
- Key Import
- [JWK Import](functions/key_import.importJWK.md#readme)
- [Public Key Import (SPKI)](functions/key_import.importSPKI.md#readme)
Expand All @@ -55,6 +55,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
- [Public Key Export](functions/key_export.exportSPKI.md#readme)
- Utilities
- [Decoding Token's Protected Header](functions/util_decode_protected_header.decodeProtectedHeader.md#readme)
- [Decoding JWT Claims Set](functions/util_decode_jwt.decodeJwt.md#readme)
- [Unsecured JWT](classes/jwt_unsecured.UnsecuredJWT.md#readme)
- [JOSE Errors](modules/util_errors.md#readme)

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js'
export type { PEMImportOptions } from './key/import.js'

export { decodeProtectedHeader } from './util/decode_protected_header.js'
export { decodeJwt } from './util/decode_jwt.js'
export type { ProtectedHeaderParameters } from './util/decode_protected_header.js'

export * as errors from './util/errors.js'
Expand Down
48 changes: 48 additions & 0 deletions src/util/decode_jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { decode as base64url } from './base64url.js'
import { decoder } from '../lib/buffer_utils.js'
import isObject from '../lib/is_object.js'
import type { JWTPayload } from '../types.d'
import { JWTInvalid } from './errors.js'

/**
* Decodes a signed JSON Web Token payload. This does not validate the JWT Claims Set
* types or values. This does not validate the JWS Signature. For a proper
* Signed JWT Claims Set validation and JWS signature verification use `jose.jwtVerify()`.
* For an encrypted JWT Claims Set validation and JWE decryption use `jose.jwtDecrypt()`.
*
* @param jwt JWT token in compact JWS serialization.
*
* @example Usage
* ```js
* const claims = jose.decodeJwt(token)
* console.log(claims)
* ```
*/
export function decodeJwt(jwt: string) {
if (typeof jwt !== 'string')
throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string')

const { 1: payload, length } = jwt.split('.')

if (length === 5) throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded')
if (length !== 3) throw new JWTInvalid('Invalid JWT')
if (!payload) throw new JWTInvalid('JWTs must contain a payload')

let decoded: Uint8Array
try {
decoded = base64url(payload)
} catch {
throw new JWTInvalid('Failed to parse the base64url encoded payload')
}

let result: unknown
try {
result = JSON.parse(decoder.decode(decoded))
} catch {
throw new JWTInvalid('Failed to parse the decoded payload as JSON')
}

if (!isObject<JWTPayload>(result)) throw new JWTInvalid('Invalid JWT Claims Set')

return result
}
54 changes: 54 additions & 0 deletions test/util/decode_jwt.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import test from 'ava'

const root = !('WEBCRYPTO' in process.env) ? '#dist' : '#dist/webcrypto'
const { decodeJwt, errors, base64url } = await import(root)

test('invalid inputs', (t) => {
const jwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'

const parts = jwt.split('.')

t.throws(() => decodeJwt(null), {
instanceOf: errors.JWTInvalid,
message: 'JWTs must use Compact JWS serialization, JWT must be a string',
})

t.throws(() => decodeJwt('....'), {
instanceOf: errors.JWTInvalid,
message: 'Only JWTs using Compact JWS serialization can be decoded',
})

t.throws(() => decodeJwt('.'), {
instanceOf: errors.JWTInvalid,
message: 'Invalid JWT',
})

t.throws(() => decodeJwt([parts[0], '', parts[2]].join('.')), {
instanceOf: errors.JWTInvalid,
message: 'JWTs must contain a payload',
})

t.throws(() => decodeJwt([parts[0], base64url.encode('null'), parts[2]].join('.')), {
instanceOf: errors.JWTInvalid,
message: 'Invalid JWT Claims Set',
})

t.throws(() => decodeJwt([parts[0], base64url.encode('[]'), parts[2]].join('.')), {
instanceOf: errors.JWTInvalid,
message: 'Invalid JWT Claims Set',
})

t.throws(() => decodeJwt([parts[0], base64url.encode('{"notajson'), parts[2]].join('.')), {
instanceOf: errors.JWTInvalid,
message: 'Failed to parse the decoded payload as JSON',
})

t.deepEqual(decodeJwt([parts[0], base64url.encode('{}'), parts[2]].join('.')), {})

t.deepEqual(decodeJwt(jwt), {
sub: '1234567890',
name: 'John Doe',
iat: 1516239022,
})
})

0 comments on commit 3d2a2b8

Please sign in to comment.