# Coldbox Security JWT

> Use this skill when implementing JWT (JSON Web Token) authentication in ColdBox REST APIs with CBSecurity, generating access/refresh tokens, validating bearer tokens, configuring JWT settings and secret keys, implementing token refresh endpoints, or securing API routes with JWT authentication middleware.

- Skill: `coldbox/coldbox-security-jwt` (Agent Skill)
- Install (CLI): `npx skillmds@latest add coldbox/coldbox-security-jwt`
- Raw SKILL.md: https://api.skillmd.com/api/skills/coldbox/coldbox-security-jwt/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: ColdBox (https://skillmd.com/u/coldbox)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/coldbox/coldbox-security-jwt

---


# JWT Development in ColdBox

## Overview

CBSecurity provides JWT (JSON Web Token) authentication for stateless REST APIs. JWT tokens encode user claims, are signed with a secret key, and validated on each request without server-side sessions.

## Language Mode Reference

Examples use **BoxLang (`.bx`)** syntax by default. Adapt for your target language:

| Concept | BoxLang (`.bx`) | CFML (`.cfc`) |
|---------|-----------------|---------------|
| Class declaration | `class [extends="..."] {` | `component [extends="..."] {` |
| DI annotation | `@inject` above `property name="svc";` | `property name="svc" inject="svc";` |
| View templates | `.bxm` suffix | `.cfm` / `.cfml` suffix |
| Tag prefix | `<bx:if>`, `<bx:output>`, `<bx:set>` | `<cfif>`, `<cfoutput>`, `<cfset>` |

> **CFML Compat Mode**: With BoxLang + CFML Compat module, `.bx` and `.cfc` files coexist freely. BoxLang-native classes use `class {}` (`.bx` files); CFML-compat classes use `component {}` (`.cfc` files).

## Installation

```bash
box install cbsecurity
box install jwtcfml
```

## JWT Configuration

```boxlang
// config/ColdBox.cfc
moduleSettings = {
    cbsecurity: {
        authentication: {
            provider: "JwtService@cbsecurity"
        },

        jwt: {
            issuer:    "myapp",
            audience:  "myapp-users",
            secretKey: getSystemSetting( "JWT_SECRET" ),
            expiration: 60,   // minutes

            refreshToken: {
                enabled:    true,
                expiration: 10080  // 7 days in minutes
            },

            tokenStorage: {
                enabled:  true,
                keyPrefix: "jwt_",
                provider: "CacheBox"
            }
        },

        firewall: {
            enabled: true,
            defaultAuthenticationAction: "block",
            statusCode: 401
        },

        rules: [
            // Public auth endpoints
            {
                secureList: "api.auth.login,api.auth.register,api.auth.refresh",
                whiteList: true,
                match: "event"
            },
            // All API routes require JWT
            {
                secureList: "^api\\.",
                match: "event",
                action: "block"
            }
        ]
    }
}
```

## JWT Service

```boxlang
/**
 * models/JWTService.cfc
 */
class singleton {

    property name="jwtService"  inject="JwtService@cbsecurity"
    property name="userService" inject="UserService"
    property name="bcrypt"      inject="@BCrypt"

    function generateToken( required user ) {
        return jwtService.encode( {
            sub:         user.id,
            email:       user.email,
            name:        user.name,
            roles:       user.roles,
            permissions: user.permissions,
            iat:         now(),
            exp:         dateAdd( "n", 60, now() )
        } )
    }

    function generateRefreshToken( required user ) {
        return jwtService.encode( {
            sub:  user.id,
            type: "refresh",
            iat:  now(),
            exp:  dateAdd( "d", 7, now() )
        } )
    }

    function authenticate( required username, required password ) {
        var user = userService.findByUsername( arguments.username )

        if ( !bcrypt.checkPassword( arguments.password, user.password ) ) {
            throw( type: "InvalidCredentials", message: "Invalid username or password" )
        }

        return {
            accessToken:  generateToken( user ),
            refreshToken: generateRefreshToken( user ),
            expiresIn:    3600,
            tokenType:    "Bearer"
        }
    }

    function refreshAccessToken( required refreshToken ) {
        var claims = jwtService.decode( arguments.refreshToken )

        if ( !claims.keyExists( "type" ) || claims.type != "refresh" ) {
            throw( type: "InvalidToken", message: "Not a refresh token" )
        }

        var user = userService.findById( claims.sub )
        return {
            accessToken: generateToken( user ),
            expiresIn:   3600,
            tokenType:   "Bearer"
        }
    }
}
```

**CFML (`.cfc`):**

```cfml
/**
 * models/JWTService.cfc
 */
component {

    property name="jwtService"  inject="JwtService@cbsecurity"
    property name="userService" inject="UserService"
    property name="bcrypt"      inject="@BCrypt"

    function generateToken( required user ) {
        return jwtService.encode( {
            sub:         user.id,
            email:       user.email,
            name:        user.name,
            roles:       user.roles,
            permissions: user.permissions,
            iat:         now(),
            exp:         dateAdd( "n", 60, now() )
        } )
    }

    function generateRefreshToken( required user ) {
        return jwtService.encode( {
            sub:  user.id,
            type: "refresh",
            iat:  now(),
            exp:  dateAdd( "d", 7, now() )
        } )
    }

    function authenticate( required username, required password ) {
        var user = userService.findByUsername( arguments.username )

        if ( !bcrypt.checkPassword( arguments.password, user.password ) ) {
            throw( type: "InvalidCredentials", message: "Invalid username or password" )
        }

        return {
            accessToken:  generateToken( user ),
            refreshToken: generateRefreshToken( user ),
            expiresIn:    3600,
            tokenType:    "Bearer"
        }
    }

    function refreshAccessToken( required refreshToken ) {
        var claims = jwtService.decode( arguments.refreshToken )

        if ( !claims.keyExists( "type" ) || claims.type != "refresh" ) {
            throw( type: "InvalidToken", message: "Not a refresh token" )
        }

        var user = userService.findById( claims.sub )
        return {
            accessToken: generateToken( user ),
            expiresIn:   3600,
            tokenType:   "Bearer"
        }
    }
}
```

## Auth Handler

```boxlang
/**
 * handlers/api/Auth.cfc
 */
class extends="coldbox.system.RestHandler" {

    property name="jwtService" inject="JWTService@models"

    // POST /api/auth/login
    function login( event, rc, prc ) {
        event.paramValue( "username", "" )
        event.paramValue( "password", "" )

        try {
            var tokens = jwtService.authenticate( rc.username, rc.password )
            event.getResponse()
                .setData( tokens )
                .setStatusCode( 200 )
        } catch ( InvalidCredentials e ) {
            event.getResponse()
                .setError( true )
                .addMessage( e.message )
                .setStatusCode( 401 )
        }
    }

    // POST /api/auth/refresh
    function refresh( event, rc, prc ) {
        event.paramValue( "refreshToken", "" )

        try {
            var result = jwtService.refreshAccessToken( rc.refreshToken )
            event.getResponse()
                .setData( result )
                .setStatusCode( 200 )
        } catch ( InvalidToken e ) {
            event.getResponse()
                .setError( true )
                .addMessage( "Invalid or expired refresh token" )
                .setStatusCode( 401 )
        }
    }
}
```

**CFML (`.cfc`):**

```cfml
/**
 * handlers/api/Auth.cfc
 */
component extends="coldbox.system.RestHandler" {

    property name="jwtService" inject="JWTService@models"

    // POST /api/auth/login
    function login( event, rc, prc ) {
        event.paramValue( "username", "" )
        event.paramValue( "password", "" )

        try {
            var tokens = jwtService.authenticate( rc.username, rc.password )
            event.getResponse()
                .setData( tokens )
                .setStatusCode( 200 )
        } catch ( InvalidCredentials e ) {
            event.getResponse()
                .setError( true )
                .addMessage( e.message )
                .setStatusCode( 401 )
        }
    }

    // POST /api/auth/refresh
    function refresh( event, rc, prc ) {
        event.paramValue( "refreshToken", "" )

        try {
            var result = jwtService.refreshAccessToken( rc.refreshToken )
            event.getResponse()
                .setData( result )
                .setStatusCode( 200 )
        } catch ( InvalidToken e ) {
            event.getResponse()
                .setError( true )
                .addMessage( "Invalid or expired refresh token" )
                .setStatusCode( 401 )
        }
    }
}
```

## Protected API Handler

```boxlang
/**
 * handlers/api/v1/Users.cfc
 * @secured — requires valid JWT
 */
class extends="coldbox.system.RestHandler" {

    property name="userService" inject="UserService"
    property name="cbsecurity"  inject="@cbsecurity"

    // GET /api/v1/users
    function index( event, rc, prc ) {
        event.paramValue( "page",    1 )
        event.paramValue( "perPage", 25 )

        prc.users = userService.list(
            page:    rc.page,
            perPage: rc.perPage
        )

        event.getResponse().setData( prc.users )
    }

    /**
     * Admin only
     * @secured admin
     */
    function destroy( event, rc, prc ) {
        userService.delete( rc.id )
        event.getResponse()
            .setData( {} )
            .setStatusCode( 204 )
    }
}
```

**CFML (`.cfc`):**

```cfml
/**
 * handlers/api/v1/Users.cfc
 * @secured — requires valid JWT
 */
component extends="coldbox.system.RestHandler" {

    property name="userService" inject="UserService"
    property name="cbsecurity"  inject="@cbsecurity"

    // GET /api/v1/users
    function index( event, rc, prc ) {
        event.paramValue( "page",    1 )
        event.paramValue( "perPage", 25 )

        prc.users = userService.list(
            page:    rc.page,
            perPage: rc.perPage
        )

        event.getResponse().setData( prc.users )
    }

    /**
     * Admin only
     * @secured admin
     */
    function destroy( event, rc, prc ) {
        userService.delete( rc.id )
        event.getResponse()
            .setData( {} )
            .setStatusCode( 204 )
    }
}
```

## Client Usage

```javascript
// Step 1: Login and get tokens
const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user@example.com', password: 'pass' })
})
const { accessToken, refreshToken } = await response.json()

// Step 2: Use access token on subsequent requests
const data = await fetch('/api/v1/users', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
})

// Step 3: Refresh when expired
const refreshResponse = await fetch('/api/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken })
})
```

## JWT Security Checklist

- Store `JWT_SECRET` in environment variable, never in source code
- Use sufficiently long secret (256+ bits)
- Set short expiration for access tokens (15-60 minutes)
- Use refresh tokens for long-lived sessions
- Validate issuer and audience claims
- Implement token revocation via token storage when needed
- Use HTTPS — JWT payloads are base64-encoded, not encrypted

