Skip to content

Commit 51dfe31

Browse files
committed
#resolve
* `Feature` : New setting for `jwt` struct: `issuer`, you can now set the issuer of tokens string or if not set, then cbSecurity will use the home page URI as the issuer of authority string. * `Feature` : All tokens will be validated that the same `iss` (Issuer) has granted the token
1 parent 233757b commit 51dfe31

File tree

5 files changed

+52
-38
lines changed

5 files changed

+52
-38
lines changed

ModuleConfig.cfc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ component {
6969
"enableSecurityVisualizer" : false,
7070
// JWT Settings
7171
"jwt" : {
72+
// The issuer authority for the tokens, placed in the `iss` claim
73+
"issuer" : "",
7274
// The jwt secret encoding key to use
7375
"secretKey" : getSystemSetting( "JWT_SECRET", "" ),
7476
// by default it uses the authorization bearer header, but you can also pass a custom one as well or as an rc variable.

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* ES512
1515
* `Feature` : Added a new convenience method on the JWT Service: `isTokenInStorage( token )` to verify if a token still exists in the token storage
1616
* `Feature` : If no jwt secret is given in the settings, we will dynamically generate one that will last for the duration of the application scope.
17+
* `Feature` : New setting for `jwt` struct: `issuer`, you can now set the issuer of tokens string or if not set, then cbSecurity will use the home page URI as the issuer of authority string.
18+
* `Feature` : All tokens will be validated that the same `iss` (Issuer) has granted the token
1719
* `Improve` : Ability to have defaults for all JWT settings instead of always typing them in the configs
1820
* `Improve` : More cfformating goodness!
1921
* `Bug` : Invalidation of tokens was not happening due to not using the actual key for the storage

models/jwt/JwtService.cfc

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,32 @@ component accessors="true" singleton {
5353

5454
// Default Settings
5555
variables.DEFAULT_SETTINGS = {
56+
// The jwt token issuer claim -> iss
57+
"issuer" : "",
5658
// The jwt secret encoding key
57-
"secretKey" : "",
59+
"secretKey" : "",
5860
// The Custom header to inspect for tokens
59-
"customAuthHeader" : "x-auth-token",
61+
"customAuthHeader" : "x-auth-token",
6062
// The expiration in minutes for the jwt tokens
61-
"expiration" : 60,
63+
"expiration" : 60,
6264
// If true, enables refresh tokens, longer lived tokens (not implemented yet)
63-
"enableRefreshTokens" : false,
65+
"enableRefreshTokens" : false,
6466
// The default expiration for refresh tokens, defaults to 30 days
65-
"refreshExpiration" : 43200,
67+
"refreshExpiration" : 43200,
6668
// encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512
67-
"algorithm" : "HS512",
69+
"algorithm" : "HS512",
6870
// Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding
69-
"requiredClaims" : [] ,
71+
"requiredClaims" : [],
7072
// The token storage settings
71-
"tokenStorage" : {
73+
"tokenStorage" : {
7274
// enable or not, default is true
73-
"enabled" : true,
75+
"enabled" : true,
7476
// A cache key prefix to use when storing the tokens
75-
"keyPrefix" : "cbjwt_",
77+
"keyPrefix" : "cbjwt_",
7678
// The driver to use: db, cachebox or a WireBox ID
77-
"driver" : "cachebox",
79+
"driver" : "cachebox",
7880
// Driver specific properties
79-
"properties" : {
80-
"cacheName" : "default"
81-
}
81+
"properties" : { "cacheName" : "default" }
8282
}
8383
};
8484

@@ -98,20 +98,32 @@ component accessors="true" singleton {
9898
*/
9999
function onDIComplete(){
100100
// If no settings defined, use the defaults
101-
if( !structKeyExists( variables.settings, "jwt" ) ){
101+
if ( !structKeyExists( variables.settings, "jwt" ) ) {
102102
variables.settings.jwt = variables.DEFAULT_SETTINGS;
103103
}
104104

105105
// Incorporate defaults into incoming data
106-
structAppend( variables.settings.jwt, variables.DEFAULT_SETTINGS, false );
107-
structAppend( variables.settings.jwt.tokenStorage, variables.DEFAULT_SETTINGS.tokenStorage, false );
106+
structAppend(
107+
variables.settings.jwt,
108+
variables.DEFAULT_SETTINGS,
109+
false
110+
);
111+
structAppend(
112+
variables.settings.jwt.tokenStorage,
113+
variables.DEFAULT_SETTINGS.tokenStorage,
114+
false
115+
);
108116

109117
// If no secret is defined, then let's create one dynamically
110118
if ( isNull( variables.settings.jwt.secretKey ) || !len( variables.settings.jwt.secretKey ) ) {
111119
variables.settings.jwt.secretKey = generateSecretKey( "blowfish", 448 );
112120
variables.log.warn( "No jwt secret key setting found, automatically generating one" );
113121
}
114122

123+
// Check if issuer is set, if not, default to the home page URI
124+
if ( !len( variables.settings.jwt.issuer ) ) {
125+
variables.settings.jwt.issuer = requestService.getContext().buildLink( "" );
126+
}
115127
}
116128

117129
/************************************************************************************/
@@ -170,11 +182,10 @@ component accessors="true" singleton {
170182
* @customClaims A struct of custom claims to add to the jwt token if successful.
171183
*/
172184
string function fromUser( required user, struct customClaims = {} ){
173-
var event = variables.requestService.getContext();
174185
var timestamp = now();
175186
var payload = {
176187
// Issuing authority
177-
"iss" : event.getHTMLBaseURL(),
188+
"iss" : variables.settings.jwt.issuer,
178189
// Token creation
179190
"iat" : toEpoch( timestamp ),
180191
// The subject identifier
@@ -381,12 +392,7 @@ component accessors="true" singleton {
381392

382393

383394
// Verify Expiration first
384-
if (
385-
dateCompare(
386-
( isDate( decodedToken.exp ) ? decodedToken.exp : fromEpoch( decodedToken.exp ) ),
387-
now()
388-
) < 0
389-
) {
395+
if ( dateCompare( ( isDate( decodedToken.exp ) ? decodedToken.exp : fromEpoch( decodedToken.exp ) ), now() ) < 0 ) {
390396
if ( variables.log.canWarn() ) {
391397
variables.log.warn( "Token rejected, it has expired", decodedToken );
392398
}
@@ -531,20 +537,21 @@ component accessors="true" singleton {
531537
}
532538

533539
/**
534-
* Verify an incoming token against our jwt library to check if it is valid.
540+
* Verify an incoming token against our jwt library to check if it is valid token only
541+
* No expiration or claim verification
535542
*
536543
* @token The token to validate
537544
*/
538545
boolean function verify( required token ){
539-
try{
546+
try {
540547
variables.jwt.decode(
541-
token = arguments.token,
542-
key = variables.settings.jwt.secretKey,
548+
token = arguments.token,
549+
key = variables.settings.jwt.secretKey,
543550
algorithms = variables.settings.jwt.algorithm,
544-
verify = false
551+
verify = false
545552
);
546553
return true;
547-
} catch( Any e ){
554+
} catch ( Any e ) {
548555
return false;
549556
}
550557
}
@@ -559,9 +566,10 @@ component accessors="true" singleton {
559566
struct function decode( required token ){
560567
try {
561568
return variables.jwt.decode(
562-
token = arguments.token,
563-
key = variables.settings.jwt.secretKey,
564-
algorithms = variables.settings.jwt.algorithm
569+
token = arguments.token,
570+
key = variables.settings.jwt.secretKey,
571+
algorithms = variables.settings.jwt.algorithm,
572+
claims = { "iss" : variables.settings.jwt.issuer }
565573
);
566574
} catch ( any e ) {
567575
throw(
@@ -792,4 +800,4 @@ component accessors="true" singleton {
792800
.len();
793801
}
794802

795-
}
803+
}

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ cbsecurity = {
8787
"enableSecurityVisualizer" : false,
8888
// JWT Settings
8989
"jwt" : {
90+
// The issuer authority for the tokens, placed in the `iss` claim
91+
"issuer" : "",
9092
// The jwt secret encoding key, defaults to getSystemEnv( "JWT_SECRET", "" )
9193
"secretKey" : getSystemSetting( "JWT_SECRET", "" ),
9294
// by default it uses the authorization bearer header, but you can also pass a custom one as well.

test-harness/tests/specs/integration/JWTSpec.cfc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
5757
} );
5858
} );
5959

60-
given( "an valid jwt token with no required claims and accessing a secure api call", function(){
60+
given( "a valid jwt token with no required claims and accessing a secure api call", function(){
6161
then( "it should block with no authorization", function(){
6262
getRequestContext().setValue(
6363
"x-auth-token",
@@ -72,7 +72,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
7272
} );
7373
} );
7474

75-
given( "an valid jwt token that's expired", function(){
75+
given( "a valid jwt token that's expired", function(){
7676
then( "it should block with no authorization", function(){
7777
getRequestContext().setValue(
7878
"x-auth-token",
@@ -146,7 +146,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
146146
var service = getInstance( "jwtService@cbsecurity" );
147147
var payload = {
148148
// Issuing authority
149-
"iss" : getRequestContext().getHTMLBaseURL(),
149+
"iss" : service.getSettings().jwt.issuer,
150150
// Token creation
151151
"iat" : service.toEpoch( timestamp ),
152152
// The subject identifier

0 commit comments

Comments
 (0)