-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwt.cfc
161 lines (150 loc) · 5.44 KB
/
jwt.cfc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
component hint="jwt" output="false" mixin="global"
{
public function init() {
this.version = "2.0,2.1";
return this;
}
/**
*
* Main entry point for jwt. (JSON Web Tokens)
* `encode()`, `decode()`, `verify()` and `sign()` are available.
*
* [section: Plugins]
* [category: JWT]
*
* @key Your Secret Key
* @ignoreExpiration ignore `exp` claim during verification
* @issuer add `iss` registered claims during verification
* @audience add `aud` registered claims during verification
*/
public any function jwt(
required key,
boolean ignoreExpiration="false",
string issuer="",
string audience=""
){
// Supported algorithms
var algorithmMap = {
"HS256" = "HmacSHA256",
"HS384" = "HmacSHA384",
"HS512" = "HmacSHA512"
};
/**
decode(string) as struct
Decode a JSON Web Token
*/
local.decode= function(required token){
// Token should contain 3 segments
if ( listLen(arguments.token,".") != 3 ) {
throw( message="Token should contain 3 segments", type="Invalid Token" );
}
// Get
var header = deserializeJSON($base64UrlDecode(listGetAt(arguments.token,1,".")));
var payload = deserializeJSON($base64UrlDecode(listGetAt(arguments.token,2,".")));
var signature = listGetAt(arguments.token,3,".");
// Make sure the algorithm listed in the header is supported
if ( listFindNoCase(structKeyList(algorithmMap),header.alg) == false ) {
throw( message="Algorithm not supported", type="Invalid Token" );
}
// Verify claims
if ( StructKeyExists(payload,"exp") && !ignoreExpiration ) {
if ( $epochTimeToLocalDate(payload.exp) < now() ) {
throw( message="Signature verification failed: Token expired", type="Invalid Token" );
}
}
if ( StructKeyExists(payload,"nbf") && $epochTimeToLocalDate(payload.nbf) > now() ) {
throw( message="Signature verification failed: Token not yet active", type="Invalid Token" );
}
if ( StructKeyExists(payload,"iss") && issuer != "" && payload.iss != issuer ) {
throw( message="Signature verification failed: Issuer does not match", type="Invalid Token" );
}
if ( StructKeyExists(payload,"aud") && audience != "" && payload.aud != audience ) {
throw( message="Signature verification failed: Audience does not match", type="Invalid Token" );
}
// Verify signature
var signInput = listGetAt(arguments.token,1,".") & "." & listGetAt(arguments.token,2,".");
if ( signature != sign(signInput,algorithmMap[header.alg]) ) {
throw( message="Signature verification failed: Invalid key", type="Invalid Token" );
}
return payload;
};
/**
encode(struct,[string]) as String
encode a data structure as a JSON Web Token
*/
local.encode = function(required payload, algorithm="HS256"){
// Default hash algorithm
var hashAlgorithm = arguments.algorithm;
var segments = "";
// Make sure only supported algorithms are used
if ( listFindNoCase(structKeyList(algorithmMap),arguments.algorithm) ) {
hashAlgorithm = arguments.algorithm;
}
// Add Header - typ and alg fields
segments = listAppend(segments, $base64UrlEscape(toBase64(serializeJSON({ "typ" = "JWT", "alg" = hashAlgorithm }))),".");
// Add payload
segments = listAppend(segments, $base64UrlEscape(toBase64(serializeJSON(arguments.payload))),".");
segments = listAppend(segments, sign(segments,algorithmMap[hashAlgorithm]),".");
return segments;
};
/**
verify(token) as Boolean
Verify the token signature
*/
local.verify=function(required token){
var isValid = true;
try {
decode(token);
} catch (any cfcatch) {
isValid = false;
}
return isValid;
};
/**
sign(string,[string]) as String
Description: Create an MHAC of provided string using the secret key and algorithm
*/
local.sign=function(required string msg, algorithm="HmacSHA256"){
var key = createObject("java", "javax.crypto.spec.SecretKeySpec").init(key.getBytes(), arguments.algorithm);
var mac = createObject("java", "javax.crypto.Mac").getInstance(arguments.algorithm);
mac.init(key);
return $base64UrlEscape(toBase64(mac.doFinal(msg.getBytes())));
};
return local;
}
/**
$base64UrlEscape(String) as String
Escapes unsafe url characters from a base64 string
*/
function $base64UrlEscape(required str) output=false {
return reReplace(reReplace(reReplace(str, "\+", "-", "all"), "\/", "_", "all"),"=", "", "all");
}
/**
$base64UrlUnescape(String) as String
Description: restore base64 characters from an url escaped string
*/
function $base64UrlUnescape(required str) output=false {
// Unescape url characters
var base64String = reReplace(reReplace(arguments.str, "\-", "+", "all"), "\_", "/", "all");
var padding = repeatstring("=",4 - len(base64String) mod 4);
return base64String & padding;
}
/**
$base64UrlDecode(String) as String
Decode a url encoded base64 string
*/
function $base64UrlDecode(required str) output=false {
return toString(toBinary($base64UrlUnescape(arguments.str)));
}
/**
$epochTimeToLocalDate(numeric) as Datetime
Converts Epoch datetime to local date
I changed the date conversion to use Java instead of dateAdd()
because currently (12/12/2016), ACF dateAdd uses an integer so there is a limit
of 2147483647 (Tue, 19 Jan 2038 03:14:07 GMT) which i doubt anyone
will still use this in 2038 but I changed it anyway.
*/
function $epochTimeToLocalDate(required epoch) output=false {
return createObject("java", "java.util.Date").init(epoch*1000);
}
}