Skip to main content

Application Security Training Guide for Bike4Mind

1. Architecture Overview

The Bike4Mind platform is built as a monorepo with a robust security architecture that comprises:

  • Frontend: Next.js-based client application
  • Backend: API routes implemented with Next.js API routes and AWS Lambda functions
  • Core Business Logic: Housed in b4m-core packages
  • Infrastructure: Managed through AWS services via SST (Serverless Stack)

2. Authentication System

2.1 Authentication Strategies

The platform supports multiple authentication methods:

export enum AuthStrategy {
Google = 'google',
Facebook = 'facebook', // Currently commented out in implementation
Github = 'github',
Okta = 'okta',
}

Additionally, the system supports:

  • Username/password local authentication
  • JWT token-based authentication for API access

2.2 JWT Implementation

JWT tokens are used for stateless authentication across the application:

  • Access Tokens: Short-lived tokens (1 day expiry) for API authentication
  • Refresh Tokens: Longer-lived tokens (2 days expiry) to renew access without re-authentication
  • Token Storage: Client-side storage with appropriate security measures
  • Token Verification: Server-side verification using the JWT secret

2.3 Authentication Flow

  1. User logs in via a strategy (local, OAuth providers)
  2. System generates JWT tokens (access + refresh)
  3. Client includes access token in the Authorization header
  4. Server validates token and attaches user to request
  5. When token expires, refresh token is used to obtain a new access token

3. Authorization System (CASL)

The application uses CASL for fine-grained access control:

3.1 Permission Levels

export enum Permission {
read = 'read',
create = 'create',
update = 'update',
delete = 'delete',
share = 'share',
}

3.2 CASL Implementation

  • Permissions are defined in the ability.ts file
  • Each request has an attached ability object via req.ability
  • Permission checks use the pattern: req.ability.can(action, subject, conditions)
  • Special permissions exist for admin users and role-based permissions

3.3 Permission Scope Patterns

The system implements several permission patterns:

  1. Owner Permissions: Users can manage their own resources

    const ownDocumentPermission = { userId: user.id };
    allow(Permission.read, Resource, ownDocumentPermission);
  2. Shared Permissions: Resources can be shared with specific users with specific permissions

    const userWithPermissions = {
    'users.userId': user.id,
    'users.permissions': permission,
    };
  3. Group Permissions: Users inherit permissions from groups they belong to

    const groupWithPermissions = {
    'groups.groupId': { $in: user.groups },
    'groups.permissions': permission,
    };
  4. Global Permissions: Some resources can be globally readable/writable

    allow(Permission.read, resource, { isGlobalRead: true });

4. API Security

4.1 BaseApi Middleware

All API routes utilize the baseApi middleware, which:

  • Establishes database connection
  • Applies authentication middleware
  • Attaches abilities to requests
  • Handles errors consistently
  • Provides logging

Example usage:

const handler = baseApi().get(
asyncHandler(async (req, res) => {
// Authentication is automatically handled
// req.ability is available for permission checks
// req.user contains the authenticated user
})
);

4.2 Manual Permission Checks

For fine-grained control, explicit permission checks are added:

.use((req, res, next) => {
if (!req.ability?.can(Permission.create, FabFile)) {
throw new BadRequestError('Unauthorized');
}
next();
})

4.3 Async Handler

The asyncHandler wrapper provides consistent error handling:

asyncHandler(async (req, res) => {
// Your code here
// Errors are caught and formatted consistently
})

5. Data Validation & Security

5.1 Zod Schemas

The application uses Zod for robust input validation:

const loginUserSchema = z.object({
usernameOrEmail: z.string(),
password: z.string(),
metadata: z.object({...})
});

5.2 Secure Parameters

The application uses a utility for secure parameter handling:

const validatedParams = secureParameters(userInput, loginUserSchema);

5.3 Error Handling

A centralized error handler provides consistent security responses:

// In errorHandler.ts
if (error instanceof HTTPError) {
({ statusCode } = error);
additionalInfo = error.additionalInfo;
}

6. CORS and Resource Sharing

6.1 CORS Configuration

CORS is carefully configured for buckets and APIs:

cors: [
{
allowedOrigins, // Dynamically generated based on environment
allowedHeaders: ['*'],
allowedMethods: ['GET'],
},
]

6.2 Allowed Origins

Origins are restricted to known domains:

function getAllowedOrigins(domain: string | undefined): string[] {
const origins = ['https://localhost:3000', 'https://localhost:3000'];

if (domain) {
origins.push(`https://app.${domain}`);
origins.push(`https://staging.${domain}`);
}

return origins;
}

7. Secrets Management

7.1 SST Secrets

Sensitive information is managed through SST Secrets:

export function Secrets({ stack }: StackContext) {
return {
MONGODB_URI: new Config.Secret(stack, 'MONGODB_URI'),
SESSION_SECRET: new Config.Secret(stack, 'SESSION_SECRET'),
JWT_SECRET: new Config.Secret(stack, 'JWT_SECRET'),
// Other secrets...
};
}

7.2 Environment Variables

Environment-specific settings are managed through environment variables:

environment: {
SEED_APP_NAME: app.name,
SEED_STAGE_NAME: app.stage,
APP_URL: origin,
// Other environment variables...
}

8. Best Practices for Development

8.1 Authentication Rules

  • DO: Use the baseApi middleware for all API routes
  • DO: Apply the most restrictive permissions necessary
  • DON'T: Store sensitive information in local storage
  • DO: Use refresh tokens for extended sessions
  • DON'T: Expose sensitive user data in responses

8.2 Authorization Rules

  • DO: Always check permissions before modifying resources
  • DO: Use CASL's req.ability.can() for permission checks
  • DON'T: Use hardcoded user IDs for permission checks
  • DO: Apply the principle of least privilege
  • DON'T: Skip permission checks for convenience

8.3 API Security Rules

  • DO: Validate all inputs using Zod schemas
  • DO: Use asyncHandler for consistent error handling
  • DON'T: Return detailed error information in production
  • DO: Log security events for auditing
  • DON'T: Trust client-side data without validation

8.4 Resource Sharing Rules

  • DO: Use proper CORS configuration
  • DO: Restrict allowed origins to known domains
  • DON'T: Use wildcards in CORS configuration in production
  • DO: Implement proper content security policies
  • DON'T: Allow unnecessary HTTP methods

9. Security Checklist for New Features

When developing new features, ensure you:

  • Apply authentication to all API routes
  • Define clear permission models for new resources
  • Validate all inputs with Zod schemas
  • Handle errors consistently using asyncHandler
  • Log security-relevant events
  • Review CORS implications for new resources
  • Follow the principle of least privilege
  • Test for common security vulnerabilities

10. Security Testing

The application should be tested for:

  1. Authentication bypass: Ensure unauthenticated users cannot access protected resources
  2. Authorization bypass: Ensure users cannot access resources they don't have permission for
  3. Input validation: Test with malformed inputs to ensure validation works
  4. Error handling: Ensure errors don't expose sensitive information
  5. JWT security: Test token expiry, refresh flows, and revocation

Conclusion

The Bike4Mind platform implements a robust security architecture using industry best practices. By following the guidelines in this document, developers can maintain and enhance the security posture of the application. Always prioritize security in your development process, and consult with the security team if you have any questions or concerns.