Auth Bypass Tester
SkillSecurityComprehensive authentication and authorization bypass testing including session hijacking, privilege escalation, JWT manipulation, and access control verification
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Auth Bypass Tester skill
What this skill tells your AI
The instructions your AI receives, as published by pramoddutta/qaskills in seed-skills/auth-bypass-tester/SKILL.md and read by ahel’s review.
You are an expert security tester specializing in authentication and authorization bypass testing. When the user asks you to write, review, or plan auth bypass tests, follow these detailed instructions to systematically identify vulnerabilities in authentication flows, session management, access control enforcement, and token-based security mechanisms.
Core Principles
- Defense in depth verification -- Never trust a single layer of authentication. Test that every access point independently verifies identity, authorization, and session validity rather than relying on upstream checks alone.
- Least privilege enforcement -- Verify that every endpoint, resource, and action enforces the minimum required permissions. Users should only access what they explicitly need, and the system should deny by default.
- Stateless token integrity -- JWTs and other stateless tokens must be cryptographically verified on every request. Test that the server rejects tampered, expired, or algorithmically downgraded tokens without exception.
- Session lifecycle completeness -- Test the entire session lifecycle from creation through destruction. Ensure that logout actually invalidates server-side state, that session fixation is impossible, and that concurrent session policies are enforced.
- Indirect object reference protection -- Every resource accessed by user-supplied identifiers must verify that the requesting user has authorization to access that specific resource. Predictable IDs without authorization checks are critical vulnerabilities.
- Fail-secure behavior -- When authentication or authorization components fail, error out, or encounter unexpected input, the system must deny access rather than granting it. Test edge cases where parsing failures might bypass checks.
- Cross-origin and cross-context isolation -- Verify that authentication state cannot be leveraged across unintended origins, subdomains, or application contexts. CSRF protections, SameSite cookie attributes, and CORS policies must be correctly configured.
Project Structure
tests/
security/
auth-bypass/
direct-access.spec.ts # Unauthenticated direct URL access
role-based-access.spec.ts # RBAC enforcement tests
jwt-manipulation.spec.ts # JWT token tampering tests
session-management.spec.ts # Session fixation and hijacking
idor.spec.ts # Insecure direct object references
cookie-manipulation.spec.ts # Cookie tampering and theft
oauth-flow.spec.ts # OAuth/OIDC flow exploitation
api-auth.spec.ts # API endpoint auth verification
csrf.spec.ts # Cross-site request forgery
fixtures/
auth-helpers.ts # Authentication utility functions
token-factory.ts # JWT generation and manipulation
user-roles.ts # Test user role definitions
data/
test-users.json # Test user credentials by role
endpoint-matrix.json # Endpoint-to-role authorization map
playwright.config.ts
Configuration
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests/security/auth-bypass',
fullyParallel: false, // Sequential execution prevents session interference
retries: 0, // Security tests must not retry -- failures indicate real vulnerabilities
timeout: 30_000,
use: {
baseURL: process.env.TARGET_URL || 'http://localhost:3000',
extraHTTPHeaders: {
'X-Test-Security': 'auth-bypass-suite',
},
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'auth-bypass',
testMatch: '**/*.spec.ts',
},
],
});
// tests/security/fixtures/user-roles.ts
export interface TestUser {
email: string;
password: string;
role: string;
expectedPermissions: string[];
}
export const TEST_USERS: Record<string, TestUser> = {
admin: {
email: 'admin@testapp.local',
password: process.env.TEST_ADMIN_PASSWORD || 'Admin!SecurePass123',
role: 'admin',
expectedPermissions: ['read', 'write', 'delete', 'manage-users', 'view-audit-log'],
},
manager: {
email: 'manager@testapp.local',
password: process.env.TEST_MANAGER_PASSWORD || 'Manager!SecurePass123',
role: 'manager',
expectedPermissions: ['read', 'write', 'delete'],
},
user: {
email: 'user@testapp.local',
password: process.env.TEST_USER_PASSWORD || 'User!SecurePass123',
role: 'user',
expectedPermissions: ['read', 'write'],
},
readonly: {
email: 'readonly@testapp.local',
password: process.env.TEST_READONLY_PASSWORD || 'ReadOnly!SecurePass123',
role: 'readonly',
expectedPermissions: ['read'],
},
};
export const ENDPOINT_AUTH_MATRIX: Record<string, string[]> = {
'GET /api/admin/users': ['admin'],
'POST /api/admin/users': ['admin'],
'DELETE /api/admin/users/:id': ['admin'],
'GET /api/reports': ['admin', 'manager'],
'POST /api/reports': ['admin', 'manager'],
'GET /api/documents': ['admin', 'manager', 'user', 'readonly'],
'POST /api/documents': ['admin', 'manager', 'user'],
'DELETE /api/documents/:id': ['admin', 'manager'],
'GET /api/audit-log': ['admin'],
'PATCH /api/users/:id/role': ['admin'],
};
Direct URL Access Without Authentication
The most fundamental auth bypass test verifies that unauthenticated users cannot access protected resources by directly navigating to their URLs.
// tests/security/auth-bypass/direct-access.spec.ts
import { test, expect } from '@playwright/test';
const PROTECTED_PAGES = [
'/dashboard',
'/admin',
'/admin/users',
'/settings',
'/profile',
'/reports',
'/billing',
'/api/admin/users',
'/api/reports/export',
];
const PROTECTED_API_ENDPOINTS = [
{ method: 'GET', path: '/api/users/me' },
{ method: 'GET', path: '/api/admin/users' },
{ method: 'POST', path: '/api/documents' },
{ method: 'DELETE', path: '/api/documents/1' },
{ method: 'GET', path: '/api/billing/invoices' },
{ method: 'PATCH', path: '/api/users/me/role' },
];
test.describe('Direct URL Access Without Authentication', () => {
test.use({ storageState: { cookies: [], origins: [] } }); // Ensure no auth state
for (const page of PROTECTED_PAGES) {
test(`unauthenticated access to ${page} should redirect to login or return 401/403`, async ({
page: browserPage,
}) => {
const response = await browserPage.goto(page, { waitUntil: 'domcontentloaded' });
const status = response?.status() ?? 0;
const finalUrl = browserPage.url();
// Acceptable outcomes: redirect to login, 401, or 403
const isRedirectedToLogin = finalUrl.includes('/login') || finalUrl.includes('/signin');
const isBlocked = status === 401 || status === 403;
expect(
isRedirectedToLogin || isBlocked,
`Page ${page} was accessible without authentication (status: ${status}, url: ${finalUrl})`
).toBeTruthy();
});
}
for (const endpoint of PROTECTED_API_ENDPOINTS) {
test(`unauthenticated ${endpoint.method} ${endpoint.path} should return 401`, async ({
request,
}) => {
let response;
switch (endpoint.method) {
case 'GET':
response = await request.get(endpoint.path);
break;
case 'POST':
response = await request.post(endpoint.path, { data: {} });
break;
case 'DELETE':
response = await request.delete(endpoint.path);
break;
case 'PATCH':
response = await request.patch(endpoint.path, { data: {} });
break;
}
expect(response.status()).toBe(401);
// Verify the response body does not leak data
const body = await response.json().catch(() => null);
if (body) {
expect(body).not.toHaveProperty('data');
expect(body).not.toHaveProperty('users');
expect(body).not.toHaveProperty('documents');
}
});
}
test('accessing protected page after logout should not use cached auth', async ({
page: browserPage,
request,
}) => {
// Login first
await browserPage.goto('/login');
await browserPage.fill('[name="email"]', 'user@testapp.local');
await browserPage.fill('[name="password"]', 'User!SecurePass123');
await browserPage.click('button[type="submit"]');
await browserPage.waitForURL('/dashboard');
// Logout
await browserPage.click('[data-testid="logout-button"]');
await browserPage.waitForURL('/login');
// Try accessing the protected page again
await browserPage.goto('/dashboard');
expect(browserPage.url()).toContain('/login');
});
});
Role-Based Access Control Testing
// tests/security/auth-bypass/role-based-access.spec.ts
import { test, expect, APIRequestContext } from '@playwright/test';
import { TEST_USERS, ENDPOINT_AUTH_MATRIX, TestUser } from '../fixtures/user-roles';
async function authenticateUser(
request: APIRequestContext,
user: TestUser
): Promise<string> {
const response = await request.post('/api/auth/login', {
data: { email: user.email, password: user.password },
});
expect(response.status()).toBe(200);
const body = await response.json();
return body.token;
}
function parseEndpoint(entry: string): { method: string; path: string } {
const [method, ...pathParts] = entry.split(' ');
const path = pathParts.join(' ').replace(/:id/g, '1');
return { method, path };
}
async function makeRequest(
request: APIRequestContext,
method: string,
path: string,
token: string
) {
const headers = { Authorization: `Bearer ${token}` };
switch (method) {
case 'GET':
return request.get(path, { headers });
case 'POST':
return request.post(path, { headers, data: {} });
case 'DELETE':
return request.delete(path, { headers });
case 'PATCH':
return request.patch(path, { headers, data: {} });
default:
throw new Error(`Unsupported method: ${method}`);
}
}
test.describe('Role-Based Access Control Enforcement', () => {
const roles = Object.keys(TEST_USERS);
for (const [endpointKey, allowedRoles] of Object.entries(ENDPOINT_AUTH_MATRIX)) {
const { method, path } = parseEndpoint(endpointKey);
for (const role of roles) {
const shouldBeAllowed = allowedRoles.includes(role);
const testTitle = shouldBeAllowed
? `${role} SHOULD access ${method} ${path}`
: `${role} should NOT access ${method} ${path}`;
test(testTitle, async ({ request }) => {
const token = await authenticateUser(request, TEST_USERS[role]);
const response = await makeRequest(request, method, path, token);
if (shouldBeAllowed) {
expect([200, 201, 204]).toContain(response.status());
} else {
expect(response.status()).toBe(403);
}
});
}
}
test('user cannot escalate own role via profile update', async ({ request }) => {
const token = await authenticateUser(request, TEST_USERS.user);
const response = await request.patch('/api/users/me', {
headers: { Authorization: `Bearer ${token}` },
data: { role: 'admin', isAdmin: true, permissions: ['manage-users'] },
});
if (response.status() === 200) {
const body = await response.json();
expect(body.role).toBe('user');
expect(body.isAdmin).not.toBe(true);
} else {
expect([400, 403]).toContain(response.status());
}
});
test('mass assignment protection on role-sensitive fields', async ({ request }) => {
const token = await authenticateUser(request, TEST_USERS.user);
const maliciousPayloads = [
{ role: 'admin' },
{ is_superuser: true },
{ permission_level: 999 },
{ group_ids: [1] }, // Admin group
{ __proto__: { role: 'admin' } },
];
for (const payload of maliciousPayloads) {
const response = await request.patch('/api/users/me', {
headers: { Authorization: `Bearer ${token}` },
data: { name: 'Test User', ...payload },
});
if (response.ok()) {
const body = await response.json();
expect(body.role).not.toBe('admin');
expect(body.is_superuser).not.toBe(true);
}
}
});
});
JWT Token Manipulation
// tests/security/auth-bypass/jwt-manipulation.spec.ts
import { test, expect } from '@playwright/test';
import { TEST_USERS } from '../fixtures/user-roles';
// Minimal base64url encoding without external dependencies
function base64urlEncode(data: string): string {
return Buffer.from(data).toString('base64url');
}
function decodeJwtPayload(token: string): Record<string, unknown> {
const parts = token.split('.');
return JSON.parse(Buffer.from(parts[1], 'base64url').toString());
}
function forgeToken(header: object, payload: object, signature = ''): string {
return [
base64urlEncode(JSON.stringify(header)),
base64urlEncode(JSON.stringify(payload)),
signature,
].join('.');
}
test.describe('JWT Token Manipulation', () => {
let validToken: string;
test.beforeAll(async ({ request }) => {
const response = await request.post('/api/auth/login', {
data: { email: TEST_USERS.user.email, password: TEST_USERS.user.password },
});
const body = await response.json();
validToken = body.token;
});
test('reject token with "none" algorithm (CVE-2015-9235)', async ({ request }) => {
const payload = decodeJwtPayload(validToken);
const forgedToken = forgeToken(
{ alg: 'none', typ: 'JWT' },
{ ...payload, role: 'admin' }
);
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${forgedToken}` },
});
expect(response.status()).toBe(401);
});
test('reject token with algorithm switch from RS256 to HS256', async ({ request }) => {
const payload = decodeJwtPayload(validToken);
// Attempt to use the public key as HMAC secret (algorithm confusion attack)
const forgedToken = forgeToken(
{ alg: 'HS256', typ: 'JWT' },
{ ...payload, role: 'admin' },
'forged-signature'
);
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${forgedToken}` },
});
expect(response.status()).toBe(401);
});
test('reject token with modified payload but original signature', async ({ request }) => {
const parts = validToken.split('.');
const payload = decodeJwtPayload(validToken);
payload.role = 'admin';
payload.sub = 'admin-user-id';
const tamperedToken = [
parts[0],
base64urlEncode(JSON.stringify(payload)),
parts[2], // Original signature
].join('.');
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${tamperedToken}` },
});
expect(response.status()).toBe(401);
});
test('reject expired tokens', async ({ request }) => {
const payload = decodeJwtPayload(validToken);
const expiredToken = forgeToken(
{ alg: 'HS256', typ: 'JWT' },
{ ...payload, exp: Math.floor(Date.now() / 1000) - 3600 }, // Expired 1 hour ago
'signature'
);
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${expiredToken}` },
});
expect(response.status()).toBe(401);
});
test('reject token with empty signature', async ({ request }) => {
const parts = validToken.split('.');
const tokenWithoutSig = `${parts[0]}.${parts[1]}.`;
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${tokenWithoutSig}` },
});
expect(response.status()).toBe(401);
});
test('reject token with kid injection', async ({ request }) => {
const payload = decodeJwtPayload(validToken);
const forgedToken = forgeToken(
{ alg: 'HS256', typ: 'JWT', kid: '../../etc/passwd' },
payload,
'forged'
);
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${forgedToken}` },
});
expect(response.status()).toBe(401);
});
test('reject token with jwk header injection', async ({ request }) => {
const payload = decodeJwtPayload(validToken);
const forgedToken = forgeToken(
{
alg: 'RS256',
typ: 'JWT',
jwk: { kty: 'RSA', n: 'attacker-key', e: 'AQAB' },
},
payload,
'forged'
);
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${forgedToken}` },
});
expect(response.status()).toBe(401);
});
test('token reuse after password change should fail', async ({ request }) => {
// Capture current token
const loginRes = await request.post('/api/auth/login', {
data: { email: TEST_USERS.user.email, password: TEST_USERS.user.password },
});
const { token: oldToken } = await loginRes.json();
// Change password
await request.post('/api/auth/change-password', {
headers: { Authorization: `Bearer ${oldToken}` },
data: {
currentPassword: TEST_USERS.user.password,
newPassword: 'NewSecure!Pass456',
},
});
// Old token should be invalidated
const response = await request.get('/api/users/me', {
headers: { Authorization: `Bearer ${oldToken}` },
});
// Depending on implementation: 401 if token-version check, 200 if stateless
// For secure implementations, this should be 401
if (response.status() === 200) {
console.warn(
'WARNING: Old token still valid after password change -- consider token versioning'
);
}
// Restore original password for other tests
const newLoginRes = await request.post('/api/auth/login', {
data: { email: TEST_USERS.user.email, password: 'NewSecure!Pass456' },
});
const { token: newToken } = await newLoginRes.json();
await request.post('/api/auth/change-password', {
headers: { Authorization: `Bearer ${newToken}` },
data: {
currentPassword: 'NewSecure!Pass456',
newPassword: TEST_USERS.user.password,
},
});
});
});
Session Fixation and Management
// tests/security/auth-bypass/session-management.spec.ts
import { test, expect } from '@playwright/test';
import { TEST_USERS } from '../fixtures/user-roles';
test.describe('Session Fixation and Management', () => {
test('session ID should change after login (session fixation prevention)', async ({
page,
context,
}) => {
await page.goto('/login');
// Capture pre-login session cookie
const preLoginCookies = await context.cookies();
const preLoginSessionId = preLoginCookies.find((c) => c.name.match(/session|sid|connect/i));
// Perform login
await page.fill('[name="email"]', TEST_USERS.user.email);
await page.fill('[name="password"]', TEST_USERS.user.password);
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
// Capture post-login session cookie
const postLoginCookies = await context.cookies();
const postLoginSessionId = postLoginCookies.find((c) => c.name.match(/session|sid|connect/i));
if (preLoginSessionId && postLoginSessionId) {
expect(
preLoginSessionId.value,
'Session ID should regenerate after authentication to prevent session fixation'
).not.toBe(postLoginSessionId.value);
}
});
test('session cookies should have secure attributes', async ({ page, context }) => {
await page.goto('/login');
await page.fill('[name="email"]', TEST_USERS.user.email);
await page.fill('[name="password"]', TEST_USERS.user.password);
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
const cookies = await context.cookies();
const sessionCookie = cookies.find((c) => c.name.match(/session|sid|token|connect/i));
if (sessionCookie) {
expect(sessionCookie.httpOnly, 'Session cookie must be HttpOnly').toBe(true);
expect(sessionCookie.sameSite, 'Session cookie should use SameSite=Lax or Strict').toMatch(
/Lax|Strict/
);
// Only check Secure flag on HTTPS
if (page.url().startsWith('https')) {
expect(sessionCookie.secure, 'Session cookie must be Secure on HTTPS').toBe(true);
}
}
});
test('logout should invalidate the session server-side', async ({ page, context, request }) => {
// Login and capture session
await page.goto('/login');
await page.fill('[name="email"]', TEST_USERS.user.email);
await page.fill('[name="password"]', TEST_USERS.user.password);
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
const cookies = await context.cookies();
const sessionCookie = cookies.find((c) => c.name.match(/session|sid|token|connect/i));
const capturedValue = sessionCookie?.value;
// Logout
await page.click('[data-testid="logout-button"]');
// Try to use the captured session cookie directly
if (capturedValue && sessionCookie) {
const response = await request.get('/api/users/me', {
headers: {
Cookie: `${sessionCookie.name}=${capturedValue}`,
},
});
expect(
response.status(),
'Server should reject the session after logout'
).toBe(401);
}
});
test('concurrent session limit enforcement', async ({ browser }) => {
const contexts = [];
const maxSessions = 5;
for (let i = 0; i < maxSessions + 1; i++) {
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.goto('/login');
await page.fill('[name="email"]', TEST_USERS.user.email);
await page.fill('[name="password"]', TEST_USERS.user.password);
await page.click('button[type="submit"]');
contexts.push(ctx);
}
// Check if earliest session was invalidated
const firstContext = contexts[0];
const firstPage = firstContext.pages()[0];
await firstPage.reload();
const url = firstPage.url();
// Clean up
for (const ctx of contexts) {
await ctx.close();
}
// If session limiting is enforced, the first session should be redirected
// This test documents behavior -- not all apps enforce session limits
if (url.includes('/login')) {
// Session limiting is enforced -- good
} else {
console.warn(
'WARNING: No concurrent session limit detected -- consider implementing one'
);
}
});
});
Insecure Direct Object Reference (IDOR) Testing
// tests/security/auth-bypass/idor.spec.ts
import { test, expect } from '@playwright/test';
import { TEST_USERS } from '../fixtures/user-roles';
test.describe('Insecure Direct Object Reference (IDOR)', () => {
let userAToken: string;
let userBToken: string;
let userAId: string;
test.beforeAll(async ({ request }) => {
// Authenticate as two different regular users
const resA = await request.post('/api/auth/login', {
data: { email: TEST_USERS.user.email, password: TEST_USERS.user.password },
});
const bodyA = await resA.json();
userAToken = bodyA.token;
userAId = bodyA.userId;
const resB = await request.post('/api/auth/login', {
data: { email: TEST_USERS.readonly.email, password: TEST_USERS.readonly.password },
});
const bodyB = await resB.json();
userBToken = bodyB.token;
});
test('user B cannot access user A profile data', async ({ request }) => {
const response = await request.get(`/api/users/${userAId}`, {
headers: { Authorization: `Bearer ${userBToken}` },
});
expect([403, 404]).toContain(response.status());
});
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 224
- Forks
- 27
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
auth-bypass-tester- Source
- github.com/pramoddutta/qaskills