1 module hunt.jwt.JwtToken; 2 3 import hunt.jwt.Base64Codec; 4 import hunt.jwt.Claims; 5 import hunt.jwt.Component; 6 import hunt.jwt.Exceptions; 7 import hunt.jwt.Header; 8 import hunt.jwt.Jwt; 9 import hunt.jwt.JwtAlgorithm; 10 import hunt.jwt.JwtOpenSSL; 11 12 import std.conv; 13 import std.datetime; 14 import std.json; 15 import std.string; 16 17 import hunt.logging; 18 19 20 /** 21 * represents a token 22 */ 23 class JwtToken { 24 25 private { 26 Claims _claims; 27 Header _header; 28 29 this(Claims claims, Header header) { 30 this._claims = claims; 31 this._header = header; 32 } 33 34 @property string data() { 35 return this.header.base64 ~ "." ~ this.claims.base64; 36 } 37 } 38 39 this(in JwtAlgorithm alg, in string typ = "JWT") { 40 this._claims = new Claims(); 41 this._header = new Header(alg, typ); 42 } 43 44 @property Claims claims() { 45 return this._claims; 46 } 47 48 @property Header header() { 49 return this._header; 50 } 51 52 /** 53 * used to get the signature of the token 54 * Parmas: 55 * secret = the secret key used to sign the token 56 * Returns: the signature of the token 57 */ 58 string signature(string secret) { 59 return Base64URLNoPadding.encode(cast(ubyte[])sign(this.data, secret, this.header.alg)); 60 61 } 62 63 /** 64 * encodes the token 65 * Params: 66 * secret = the secret key used to sign the token 67 *Returns: base64 representation of the token including signature 68 */ 69 string encode(string secret) { 70 if ((this.claims.exp != ulong.init && this.claims.iat != ulong.init) && this.claims.exp < this.claims.iat) { 71 throw new ExpiredException("Token has already expired"); 72 } 73 74 if ((this.claims.exp != ulong.init && this.claims.nbf != ulong.init) && this.claims.exp < this.claims.nbf) { 75 throw new ExpiresBeforeValidException("Token will expire before it becomes valid"); 76 } 77 78 string token = this.data ~ "." ~ this.signature(secret); 79 80 version(HUNT_AUTH_DEBUG) { 81 import std.stdio; 82 writeln("secret: %s, token: %s", secret, token); 83 } 84 85 return token; 86 87 } 88 /// 89 unittest { 90 JwtToken token = new JwtToken(JwtAlgorithm.HS512); 91 92 long now = Clock.currTime.toUnixTime(); 93 94 string secret = "super_secret"; 95 token.claims.exp = now - 3600; 96 97 assertThrown!ExpiredException(token.encode(secret)); 98 99 token.claims.exp = now + 3600; 100 token.claims.nbf = now + 7200; 101 102 assertThrown!ExpiresBeforeValidException(token.encode(secret)); 103 } 104 105 /** 106 * overload of the encode(string secret) function to simplify encoding of token without algorithm none 107 * Returns: base64 representation of the token 108 */ 109 string encode() { 110 assert(this.header.alg == JwtAlgorithm.NONE); 111 return this.encode(""); 112 } 113 114 115 static JwtToken decode(string token, string delegate(ref JSONValue jose) lazyKey) { 116 import std.algorithm : count; 117 import std.conv : to; 118 import std.uni : toUpper; 119 120 version(HUNT_JWT_DEBUG) { 121 tracef("token: %s", token); 122 } 123 124 if(count(token, ".") != 2) 125 throw new VerifyException("Token is incorrect."); 126 127 string[] tokenParts = split(token, "."); 128 129 JSONValue header; 130 try { 131 header = parseJSON(cast(string)urlsafeB64Decode(tokenParts[0])); 132 } catch(Exception e) { 133 throw new VerifyException("Header is incorrect."); 134 } 135 136 JwtAlgorithm alg; 137 try { 138 // toUpper for none 139 alg = to!(JwtAlgorithm)(toUpper(header["alg"].str())); 140 } catch(Exception e) { 141 throw new VerifyException("Algorithm is incorrect."); 142 } 143 144 if (auto typ = ("typ" in header)) { 145 string typ_str = typ.str(); 146 if(typ_str && typ_str != "JWT") 147 throw new VerifyException("Type is incorrect."); 148 } 149 150 const key = lazyKey(header); 151 if(!key.empty() && !verifySignature(tokenParts[0]~"."~tokenParts[1], tokenParts[2], key, alg)) 152 throw new VerifyException("Signature is incorrect."); 153 154 JSONValue payload; 155 156 try { 157 payload = parseJSON(cast(string)urlsafeB64Decode(tokenParts[1])); 158 } catch(JSONException e) { 159 // Code coverage has to miss this line because the signature test above throws before this does 160 throw new VerifyException("Payload JSON is incorrect."); 161 } 162 163 164 Header h = new Header(header); 165 Claims claims = new Claims(payload); 166 167 return new JwtToken(claims, h); 168 } 169 170 static JwtToken decode(string encodedToken, string key="") { 171 return decode(encodedToken, (ref _) => key); 172 } 173 174 static bool verify(string token, string key) { 175 import std.algorithm : count; 176 import std.conv : to; 177 import std.uni : toUpper; 178 179 if(count(token, ".") != 2) 180 throw new VerifyException("Token is incorrect."); 181 182 string[] tokenParts = split(token, "."); 183 184 string decHeader = cast(string)urlsafeB64Decode(tokenParts[0]); 185 JSONValue header = parseJSON(decHeader); 186 187 JwtAlgorithm alg; 188 try { 189 // toUpper for none 190 alg = to!(JwtAlgorithm)(toUpper(header["alg"].str())); 191 } catch(Exception e) { 192 throw new VerifyException("Algorithm is incorrect."); 193 } 194 195 if (auto typ = ("typ" in header)) { 196 string typ_str = typ.str(); 197 if(typ_str && typ_str != "JWT") 198 throw new VerifyException("Type is incorrect."); 199 } 200 201 return verifySignature(tokenParts[0]~"."~tokenParts[1], tokenParts[2], key, alg); 202 } 203 } 204 205 206 alias verify = JwtToken.verify; 207 alias decode = JwtToken.decode; 208 209 deprecated("Using JwtToken instead.") 210 alias Token = JwtToken;