mirror of
https://github.com/jonasroussel/dart_jsonwebtoken.git
synced 2025-07-31 16:05:30 +08:00
v1.0.0
This commit is contained in:
@ -1,3 +1,10 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- New RSA Algorithm (RS256)
|
||||||
|
- Keys a now using an abstract class 'Key' instead of a string
|
||||||
|
- SecretKey: for HMAC (HS256)
|
||||||
|
- PrivateKey & PublicKey: for RSA (RS256)
|
||||||
|
|
||||||
## 0.2.1
|
## 0.2.1
|
||||||
|
|
||||||
- Formatting
|
- Formatting
|
||||||
|
17
README.md
17
README.md
@ -1,8 +1,11 @@
|
|||||||
# JsonWebToken
|
# JSON Web Token
|
||||||
[](https://pub.dev/packages/dart_jsonwebtoken)
|
[](https://pub.dev/packages/dart_jsonwebtoken)
|
||||||
|
|
||||||
A dart implementation of the famous javascript library `jsonwebtoken`.
|
A dart implementation of the famous javascript library `jsonwebtoken`.
|
||||||
|
|
||||||
|
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
|
||||||
|
https://jwt.io allows you to decode, verify and generate JWT.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Import
|
### Import
|
||||||
@ -25,8 +28,8 @@ final jwt = JWT(
|
|||||||
issuer: 'https://github.com/jonasroussel/jsonwebtoken',
|
issuer: 'https://github.com/jonasroussel/jsonwebtoken',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sign it
|
// Sign it (default with HS256 algorithm)
|
||||||
token = jwt.sign('secret-key');
|
token = jwt.sign(SecretKey('secret passphrase'));
|
||||||
|
|
||||||
print('Signed token: $token\n');
|
print('Signed token: $token\n');
|
||||||
```
|
```
|
||||||
@ -36,7 +39,7 @@ print('Signed token: $token\n');
|
|||||||
```dart
|
```dart
|
||||||
try {
|
try {
|
||||||
// Verify a token
|
// Verify a token
|
||||||
final jwt = JWT.verify(token, 'secret-key');
|
final jwt = JWT.verify(token, SecretKey('secret passphrase'));
|
||||||
|
|
||||||
print('Payload: ${jwt.payload}');
|
print('Payload: ${jwt.payload}');
|
||||||
} on JWTExpiredError {
|
} on JWTExpiredError {
|
||||||
@ -45,3 +48,9 @@ try {
|
|||||||
print(ex.message); // ex: invalid signature
|
print(ex.message); // ex: invalid signature
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Supported Algorithms
|
||||||
|
|
||||||
|
- HS256 (HMAC / SHA256)
|
||||||
|
- RS256 (RSA / SHA256)
|
||||||
|
@ -17,7 +17,7 @@ main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Sign it
|
// Sign it
|
||||||
token = jwt.sign('secret-key');
|
token = jwt.sign(SecretKey('secret passphrase'));
|
||||||
|
|
||||||
print('Signed token: $token\n');
|
print('Signed token: $token\n');
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ main() {
|
|||||||
/* Verify */ {
|
/* Verify */ {
|
||||||
try {
|
try {
|
||||||
// Verify a token
|
// Verify a token
|
||||||
final jwt = JWT.verify(token, 'secret-key');
|
final jwt = JWT.verify(token, SecretKey('secret passphrase'));
|
||||||
|
|
||||||
print('Payload: ${jwt.payload}');
|
print('Payload: ${jwt.payload}');
|
||||||
} on JWTExpiredError {
|
} on JWTExpiredError {
|
||||||
|
@ -3,3 +3,4 @@ library jsonwebtoken;
|
|||||||
export 'src/jwt.dart';
|
export 'src/jwt.dart';
|
||||||
export 'src/errors.dart';
|
export 'src/errors.dart';
|
||||||
export 'src/algorithms.dart';
|
export 'src/algorithms.dart';
|
||||||
|
export 'src/keys.dart';
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||||
|
import 'package:pointycastle/pointycastle.dart' hide PrivateKey, PublicKey;
|
||||||
|
import 'package:rsa_pkcs/rsa_pkcs.dart' hide RSAPrivateKey, RSAPublicKey;
|
||||||
|
|
||||||
abstract class JWTAlgorithm {
|
abstract class JWTAlgorithm {
|
||||||
static const HS256 = HS256Algorithm();
|
static const HS256 = HS256Algorithm();
|
||||||
|
static const RS256 = RS256Algorithm();
|
||||||
|
|
||||||
static JWTAlgorithm fromName(String name) {
|
static JWTAlgorithm fromName(String name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'HS256':
|
case 'HS256':
|
||||||
return JWTAlgorithm.HS256;
|
return JWTAlgorithm.HS256;
|
||||||
|
case 'RS256':
|
||||||
|
return JWTAlgorithm.RS256;
|
||||||
default:
|
default:
|
||||||
throw JWTInvalidError('unknown algorithm');
|
throw JWTInvalidError('unknown algorithm');
|
||||||
}
|
}
|
||||||
@ -18,8 +24,8 @@ abstract class JWTAlgorithm {
|
|||||||
const JWTAlgorithm();
|
const JWTAlgorithm();
|
||||||
|
|
||||||
String get name;
|
String get name;
|
||||||
List<int> sign(String key, List<int> body);
|
List<int> sign(Key key, List<int> body);
|
||||||
bool verify(String key, List<int> body, List<int> signature);
|
bool verify(Key key, List<int> body, List<int> signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
class HS256Algorithm extends JWTAlgorithm {
|
class HS256Algorithm extends JWTAlgorithm {
|
||||||
@ -29,13 +35,18 @@ class HS256Algorithm extends JWTAlgorithm {
|
|||||||
String get name => 'HS256';
|
String get name => 'HS256';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<int> sign(String key, List<int> body) {
|
List<int> sign(Key key, List<int> body) {
|
||||||
final hmac = Hmac(sha256, utf8.encode(key));
|
assert(key is SecretKey, 'key must be a SecretKey');
|
||||||
|
final secretKey = key as SecretKey;
|
||||||
|
|
||||||
|
final hmac = Hmac(sha256, utf8.encode(secretKey.key));
|
||||||
return hmac.convert(body).bytes;
|
return hmac.convert(body).bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool verify(String key, List<int> body, List<int> signature) {
|
bool verify(Key key, List<int> body, List<int> signature) {
|
||||||
|
assert(key is SecretKey, 'key must be a SecretKey');
|
||||||
|
|
||||||
final actual = sign(key, body);
|
final actual = sign(key, body);
|
||||||
|
|
||||||
if (actual.length != signature.length) return false;
|
if (actual.length != signature.length) return false;
|
||||||
@ -47,3 +58,80 @@ class HS256Algorithm extends JWTAlgorithm {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RS256Algorithm extends JWTAlgorithm {
|
||||||
|
const RS256Algorithm();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'RS256';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<int> sign(Key key, List<int> body) {
|
||||||
|
assert(key is PrivateKey, 'key must be a PrivateKey');
|
||||||
|
final privateKey = key as PrivateKey;
|
||||||
|
|
||||||
|
final parser = RSAPKCSParser();
|
||||||
|
RSAKeyPair pair;
|
||||||
|
|
||||||
|
try {
|
||||||
|
pair = parser.parsePEM(privateKey.key, password: privateKey.passphrase);
|
||||||
|
assert(pair.private != null);
|
||||||
|
} catch (ex) {
|
||||||
|
throw JWTInvalidError('invalid private RSA key');
|
||||||
|
}
|
||||||
|
|
||||||
|
final signer = Signer('SHA-256/RSA');
|
||||||
|
final params = ParametersWithRandom(
|
||||||
|
PrivateKeyParameter<RSAPrivateKey>(
|
||||||
|
RSAPrivateKey(
|
||||||
|
pair.private.modulus,
|
||||||
|
pair.private.privateExponent,
|
||||||
|
pair.private.prime1,
|
||||||
|
pair.private.prime2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SecureRandom('AES/CTR/PRNG'),
|
||||||
|
);
|
||||||
|
|
||||||
|
signer.init(true, params);
|
||||||
|
|
||||||
|
RSASignature signature = signer.generateSignature(Uint8List.fromList(body));
|
||||||
|
|
||||||
|
return signature.bytes.toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool verify(Key key, List<int> body, List<int> signature) {
|
||||||
|
assert(key is PublicKey, 'key must be a PublicKey');
|
||||||
|
final publicKey = key as PublicKey;
|
||||||
|
|
||||||
|
final parser = RSAPKCSParser();
|
||||||
|
RSAKeyPair pair;
|
||||||
|
|
||||||
|
try {
|
||||||
|
pair = parser.parsePEM(publicKey.key, password: publicKey.passphrase);
|
||||||
|
assert(pair.public != null);
|
||||||
|
} catch (ex) {
|
||||||
|
throw JWTInvalidError('invalid public RSA key');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final signer = Signer('SHA-256/RSA');
|
||||||
|
final params = ParametersWithRandom(
|
||||||
|
PublicKeyParameter<RSAPublicKey>(
|
||||||
|
RSAPublicKey(
|
||||||
|
pair.public.modulus,
|
||||||
|
BigInt.from(pair.public.publicExponent),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SecureRandom('AES/CTR/PRNG'),
|
||||||
|
);
|
||||||
|
|
||||||
|
signer.init(false, params);
|
||||||
|
|
||||||
|
return signer.verifySignature(Uint8List.fromList(body), RSASignature(Uint8List.fromList(signature)));
|
||||||
|
} catch (ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,45 +5,39 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
|||||||
import './utils.dart';
|
import './utils.dart';
|
||||||
|
|
||||||
class JWT {
|
class JWT {
|
||||||
static JWT verify(String token, String key) {
|
static JWT verify(String token, Key key) {
|
||||||
try {
|
final parts = token.split('.');
|
||||||
final parts = token.split('.');
|
|
||||||
|
|
||||||
final header = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[0])));
|
final header = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[0])));
|
||||||
|
|
||||||
if (header['typ'] != 'JWT') throw JWTInvalidError('not a jwt');
|
if (header['typ'] != 'JWT') throw JWTInvalidError('not a jwt');
|
||||||
|
|
||||||
final algorithm = JWTAlgorithm.fromName(header['alg']);
|
final algorithm = JWTAlgorithm.fromName(header['alg']);
|
||||||
|
|
||||||
if (parts.length < 3) throw JWTInvalidError('jwt malformated');
|
if (parts.length < 3) throw JWTInvalidError('jwt malformated');
|
||||||
|
|
||||||
final body = utf8.encode(parts[0] + '.' + parts[1]);
|
final body = utf8.encode(parts[0] + '.' + parts[1]);
|
||||||
final signature = base64Url.decode(base64Padded(parts[2]));
|
final signature = base64Url.decode(base64Padded(parts[2]));
|
||||||
|
|
||||||
if (!algorithm.verify(key, body, signature)) {
|
if (!algorithm.verify(key, body, signature)) {
|
||||||
throw JWTInvalidError('invalid signature');
|
throw JWTInvalidError('invalid signature');
|
||||||
}
|
|
||||||
|
|
||||||
final payload = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[1])));
|
|
||||||
|
|
||||||
if (payload.containsKey('exp')) {
|
|
||||||
final exp = DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
|
|
||||||
if (exp.isBefore(DateTime.now())) {
|
|
||||||
throw JWTExpiredError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JWT(
|
|
||||||
payload: payload,
|
|
||||||
audience: payload.remove('aud'),
|
|
||||||
issuer: payload.remove('iss'),
|
|
||||||
subject: payload.remove('sub'),
|
|
||||||
);
|
|
||||||
} on JWTError {
|
|
||||||
rethrow;
|
|
||||||
} catch (ex) {
|
|
||||||
throw JWTInvalidError('jwt invalid');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final payload = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[1])));
|
||||||
|
|
||||||
|
if (payload.containsKey('exp')) {
|
||||||
|
final exp = DateTime.fromMillisecondsSinceEpoch(payload['exp'] * 1000);
|
||||||
|
if (exp.isBefore(DateTime.now())) {
|
||||||
|
throw JWTExpiredError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JWT(
|
||||||
|
payload: payload,
|
||||||
|
audience: payload.remove('aud'),
|
||||||
|
issuer: payload.remove('iss'),
|
||||||
|
subject: payload.remove('sub'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
JWT({
|
JWT({
|
||||||
@ -59,7 +53,7 @@ class JWT {
|
|||||||
final String issuer;
|
final String issuer;
|
||||||
|
|
||||||
String sign(
|
String sign(
|
||||||
String key, {
|
Key key, {
|
||||||
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
|
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
|
||||||
Duration expiresIn,
|
Duration expiresIn,
|
||||||
bool noTimestamp = false,
|
bool noTimestamp = false,
|
||||||
|
21
lib/src/keys.dart
Normal file
21
lib/src/keys.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
abstract class Key {}
|
||||||
|
|
||||||
|
class SecretKey extends Key {
|
||||||
|
String key;
|
||||||
|
|
||||||
|
SecretKey(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivateKey extends Key {
|
||||||
|
String key;
|
||||||
|
String passphrase;
|
||||||
|
|
||||||
|
PrivateKey(this.key, [this.passphrase = '']);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicKey extends Key {
|
||||||
|
String key;
|
||||||
|
String passphrase;
|
||||||
|
|
||||||
|
PublicKey(this.key, [this.passphrase = '']);
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
name: dart_jsonwebtoken
|
name: dart_jsonwebtoken
|
||||||
description: A dart implementation of the famous javascript library 'jsonwebtoken'.
|
description: A dart implementation of the famous javascript library 'jsonwebtoken'.
|
||||||
version: 0.2.1
|
version: 1.0.0
|
||||||
repository: https://github.com/jonasroussel/jsonwebtoken
|
repository: https://github.com/jonasroussel/jsonwebtoken
|
||||||
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
|
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.7.0 <3.0.0'
|
sdk: '>=2.3.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
crypto: ^2.1.5
|
crypto: ^2.1.5
|
||||||
|
rsa_pkcs: ^1.1.3
|
||||||
|
pointycastle: ^1.0.2
|
||||||
|
Reference in New Issue
Block a user