Skip to content

Commit

Permalink
Merge pull request #43 from irensaltali/42-improve-auth-states-and-re…
Browse files Browse the repository at this point in the history
…sponses

feat(auth)!: Add custom error handling to jwtAuth function
  • Loading branch information
irensaltali authored Mar 1, 2024
2 parents 14d08ec + b94cbbc commit e522a34
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</h1>
</div>

[![JWT COMPATIBLE](https://jwt.io/img/badge-compatible.svg)]()

Welcome to the Serverless API Gateway, an innovative tool designed to streamline your API management tasks using the powerful capabilities of Cloudflare Workers.

Expand Down
56 changes: 50 additions & 6 deletions worker/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,67 @@
import { JWTPayload, jwtVerify } from 'jose';
import { JWTPayload, jwtVerify, errors } from 'jose';
import apiConfig from './api-config.json';

// Define a custom error type for clearer error handling
class AuthError extends Error {
statusCode: number;
code: string;
constructor(message: string, code: string, statusCode: number) {
super(message);
this.name = "AuthError";
this.code = code;
this.statusCode = statusCode;
}
}

async function jwtAuth(request: Request): Promise<JWTPayload> {
try {
const secret = new TextEncoder().encode(apiConfig.authorizer?.secret);
const jwt = request.headers.get('Authorization')?.split(' ')[1] || '';
const secret = new TextEncoder().encode(apiConfig.authorizer?.secret);
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new AuthError('No token provided or token format is invalid.', "AUTH_ERROR", 401);
}
const jwt = authHeader.split(' ')[1];

try {
const { payload, protectedHeader } = await jwtVerify(jwt, secret, {
issuer: apiConfig.authorizer?.issuer,
audience: apiConfig.authorizer?.audience,
});

console.log('JWT verification successful:', payload, protectedHeader);

return payload;
} catch (error) {
console.error('JWT verification failed:', error);
return {};
if (error instanceof errors.JOSEAlgNotAllowed) {
throw new AuthError('Algorithm not allowed', error.code, 401);
} else if (error instanceof errors.JWEDecryptionFailed) {
throw new AuthError('Decryption failed', error.code, 401);
} else if (error instanceof errors.JWEInvalid) {
throw new AuthError('Invalid JWE', error.code, 401);
} else if (error instanceof errors.JWTExpired) {
throw new AuthError('Token has expired.', error.code, 401);
} else if (error instanceof errors.JWTClaimValidationFailed) {
throw new AuthError('JWT claim validation failed', error.code, 401);
} else if (error instanceof errors.JWTInvalid) {
throw new AuthError('Invalid JWT', error.code, 401);
} else if (error instanceof errors.JWKSNoMatchingKey) {
throw new AuthError('No matching key found in JWKS.', error.code, 401);
} else if (error instanceof errors.JWKSInvalid) {
throw new AuthError('Invalid JWKS', error.code, 401);
} else if (error instanceof errors.JWKSMultipleMatchingKeys) {
throw new AuthError('Multiple matching keys found in JWKS.', error.code, 401);
} else if (error instanceof errors.JWKSNoMatchingKey) {
throw new AuthError('No matching key in JWKS.', error.code, 401);
} else if (error instanceof errors.JWSInvalid) {
throw new AuthError('Invalid JWS', error.code, 401);
} else if (error instanceof errors.JWSSignatureVerificationFailed) {
throw new AuthError('Signature verification failed', error.code, 401);
} else if (error instanceof Error) {
throw new AuthError('JWT verification failed', "AUTH_ERROR", 401);
}
// Fallback in case error is not an instance of Error
throw new AuthError('JWT verification failed due to an unexpected error.', "AUTH_ERROR", 401);
}
}

export { jwtAuth }
export { jwtAuth, AuthError }
17 changes: 13 additions & 4 deletions worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import apiConfig from './api-config.json';
import { jwtAuth } from './auth';
import { jwtAuth, AuthError } from './auth';
import { setCorsHeaders } from "./cors";
import { applyValueMapping } from "./mapping";
import { setPoweredByHeader } from "./powered-by";
Expand Down Expand Up @@ -57,9 +57,18 @@ export default {
if (matchedPath) {
var jwtPayload = {};
if (apiConfig.authorizer && matchedPath.auth) {
jwtPayload = await jwtAuth(request);
if (!jwtPayload.iss) {
return setPoweredByHeader(setCorsHeaders(request, responses.unauthorizedResponse()));
try {
jwtPayload = await jwtAuth(request);
} catch (error) {
if (error instanceof AuthError) {
return setPoweredByHeader(setCorsHeaders(request, new Response(JSON.stringify({ error: error.message, code: error.code }), {
status: error.statusCode,
headers: { 'Content-Type': 'application/json' }
})));
} else {
console.error('Error during JWT verification:', error);
return setPoweredByHeader(setCorsHeaders(request, responses.internalServerErrorResponse()));
}
}
}

Expand Down
1 change: 1 addition & 0 deletions worker/src/responses.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const noMatchResponse = () => new Response( JSON.stringify({ message: 'No match found.' }), { headers: { 'Content-Type': 'application/json' }, status: 404 })
export const unauthorizedResponse = () => new Response(JSON.stringify({ message: 'Unauthorized' }),{ headers: { 'Content-Type': 'application/json' }, status: 401 })
export const internalServerErrorResponse = () => new Response(JSON.stringify({ message: 'Internal server error' }), { headers: { 'Content-Type': 'application/json' }, status: 500 })

0 comments on commit e522a34

Please sign in to comment.