JWT Services
Last updated
Last updated
CBSecurity also provides you with a JWT (Json Web Tokens) authentication and authorization system.
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.
You can find much more information about JWT at jwt.io.
JSON Web Tokens have become the standard for authenticating and authorizing API requests. They can be used on their own or with an oauth/single sign-on server as well.
Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.
The ColdBox Security module will assist you with all the generation, decoding, encoding and security aspects of JWT. All you need to do is, configure it, create a few standard files and off you go.
The tokens created by the JWT services will have the mandatory headers, but also will have a standardizes payload structure. This payload structure can also be customized as you see fit.
A JSON Web Token encodes a series of claims in a JSON object. Some of these claims have specific meanings, while others are left to be interpreted by the users. You can consider claims to be the keys of the payload structure, and it can contain pretty much anything you like.
Here are the base claims that the ColdBox Security JWT token creates for you automatically:
Issuer (iss
) - The issuer of the token (defaults to the application's base URL)
Issued At (iat
) - When the token was issued (Unix timestamp)
Subject (sub
) - This holds the identifier for the token (defaults to user id)
Expiration time (exp
) - The token expiry date (Unix timestamp)
Unique ID (jti
) - A unique identifier for the token (md5
of the sub
and iat
claims)
Scopes (scope)
- A space-delimited string of scopes attached to the token
Refresh Token (cbsecurity_refresh
) - If you use refresh tokens, this custom claim will be added to the payload.
You can add much more to this payload via the JWT service methods or the User that models the token.
The service can be found here cbsecurity.models.jwt.JWTService
and can be retrieved by either injecting the service (JwtService@cbsecurity
) or using our helper method (jwtAuth()
).
To begin exploring the JWT capabilities, let's explore how to configure it first.
You can check out our JWT Configuration section for a more in-depth tutorial. Here are the basic settings you would put in the cbsecurity
configuration:
The next step is ensuring that our JWT services can handle the construction of the JWT tokens as per YOUR requirements. So your User
object must implement our JWTSubject
interface with the following functions:
Basically, it's two functions:
getJwtCustomClaims( payload )
- This is a struct of custom claims to incorporate into the token payload at construction time. This can be ANYTHING you like.
getJwtScopes()
- We will also call this at construction time in order to incorporate the right permission scopes into the token according to your user. This must be an array of scopes/permissions.
Since also the authentication services will be used with JWT, your user object might end up looking like this:
The JWT validators must talk to the authentication and user services. Please refer to the Authentication Services page to configure and create them.
Ok, now we can focus on all the wonderful methods the JWT service offers:
attempt( username, password, [ customClaims:struct ] ):token
- Attempt to authenticate a user with the authentication service and return the token using the identifier and custom claims if successful. Exception if invalid authentication
fromUser( user, [ customClaims:struct ] ):token
- Generate a token according to the passed user object and custom claims.
encode( struct payload ):token
- Generate a raw JWT token from a native payload struct.
verify( required token ):boolean
- Verify a token string or throws an exception
decode( required token ):struct
- Decode and retrieve the passed-in token to CFML struct
parseToken( token, storeInContext, authenticate ):struct
- Get the decoded token using the headers strategy and store it in the prc.jwt_token
and the decoded data as prc.jwt_payload
if it verifies correctly. Throws: TokenExpiredException
if the token is expired, TokenInvalidException
if the token doesn't verify decoding, TokenNotFoundException
if not found
getToken():string
- Get the stored token from prc.jwt_token
, if it doesn't exist, it tries to parse it via parseToken()
, if no token is set, this will be an empty string.
getPayload():struct
- Get the stored token from prc.jwt_payload
, if it doesn't exist, it tries to parse it via parseToken()
, if no token is set, this will be an empty struct.
setToken( token ):JWTService
- Store the token in prc.jwt_token
, and store the decoded version in prc.jwt_payload
authenticate( [payload] ):User
- Authenticates a passed or detected token payload and return the user it represents
getuser()
- Get the authenticated user according to the access token detected
logout()
- Logout a user and invalidate their token
invalidateAll( async:false )
- Invalidate all access and refresh tokens in permanent storage
invalidate( token )
- Invalidates the incoming token by removing it from the permanent storage.
isTokenInStorage( token )
- Checks if the passed token exists in permanent storage.
getTokenStorage( force:false )
- Get the current token storage implementation. You can also force-create it again if needed.
attempt( username, password, [ customClaims:struct ] ):struct
- Attempt to authenticate a user with the authentication service and if successful, return a struct containing an access and refresh token.
fromUser( user, [ customClaims:struct ] ):struct
- Generate a struct of refresh and access tokens according to the passed user object and custom claims.
That's it; we are ready to put it all together. Now cbsecurity knows about your authentication/user services, can talk to your user to create tokens, and can guard the incoming requests via the JWT Validator. Here is a sample controller for login, logout, and user registration:
Let's configure some routes first:
Then build out the Auth
controller
Make sure you add validation!
That's it; we can now login a user, give them a token, register a new user and give them their token, and log them out. The next step is to build your rules and/or security annotations and ensure the JWT validator is configured for your global app or module.
To implement JWT authentication in your application, you may need to modify some web server settings. Most web servers have default content length restrictions on the size of an individual header. If your web server platform has such a default enabled, you will need to increase the buffer size to accommodate the presence of JTW tokens in both the request
and response
headers.
The size of a JWT token header, encrypted via the default cbSecurity HMAC512
algorithm, is around 44 kilobytes. As such, you will need to allow for at least that size. Below are some examples of common web server configurations
The following configuration may be applied to the main NGINX http
configuration block to allow for the presence of tokens in both the request and response headers:
You will need to modify two registry keys:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters\MaxFieldLength
- Sets an upper limit, in bytes, for each header. The default value is 65534 bytes and the maximum value is 65534 bytes ( 64kb )
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters\MaxRequestBytes
- Sets the upper limit for the request line and the headers, combined. As such 128K should allow for both long URLs, as well as JWT tokens in the headers. The default value is 16384 bytes and the maximum value is 16777216 bytes ( 16 MB )
You will need to add a LimitRequestFieldSize
setting in each <VirtualHost...>
entry in order increase the default header size from the default 8 kilobytes. Example, with a setting of 128 kilobytes: