This commit is contained in:
Jonas Roussel
2020-05-31 16:10:51 +02:00
parent a4427175b4
commit 75473b7e4b
10 changed files with 211 additions and 124 deletions

View File

@ -1,3 +1,3 @@
## 1.0.0
## 0.1.0
- Initial version, created by Stagehand
- First version with every based features

View File

@ -1,22 +1,2 @@
A library for Dart developers.
Created from templates made available by Stagehand under a BSD-style
[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE).
## Usage
A simple usage example:
```dart
import 'package:jsonwebtoken/jsonwebtoken.dart';
main() {
var awesome = new Awesome();
}
```
## Features and bugs
Please file feature requests and bugs at the [issue tracker][tracker].
[tracker]: http://example.com/issues/replaceme
# JsonWebToken
[![pub package](https://img.shields.io/pub/v/jsonwebtoken.svg)](https://pub.dev/packages/jsonwebtoken)

View File

@ -1,11 +1,37 @@
import 'package:jsonwebtoken/jsonwebtoken.dart';
main() {
final token = JWT(
payload: {
'hello': 'world',
},
).sign(key: 'test');
String token;
print(token);
/* Sign */ {
// Create a json web token
final jwt = JWT(
payload: {
'id': 123,
'server': {
'id': '3e4fc296',
'loc': 'euw-2',
}
},
issuer: 'https://github.com/jonasroussel/jsonwebtoken',
);
// Sign it
token = jwt.sign('secret-key');
print('Signed token: $token\n');
}
/* Verify */ {
try {
// Verify a token
final jwt = JWT.verify(token, 'secret-key');
print('Payload: ${jwt.payload}');
} on JWTExpiredError {
print('jwt expired');
} on JWTError catch (ex) {
print(ex.message); // ex: invalid signature
}
}
}

View File

@ -1,3 +1,5 @@
library jsonwebtoken;
export 'src/jsonwebtoken.dart';
export 'src/jwt.dart';
export 'src/errors.dart';
export 'src/algorithms.dart';

49
lib/src/algorithms.dart Normal file
View File

@ -0,0 +1,49 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:jsonwebtoken/jsonwebtoken.dart';
abstract class JWTAlgorithm {
static const HS256 = HS256Algorithm();
static JWTAlgorithm fromName(String name) {
switch (name) {
case 'HS256':
return JWTAlgorithm.HS256;
default:
throw JWTInvalidError('unknown algorithm');
}
}
const JWTAlgorithm();
String get name;
List<int> sign(String key, List<int> body);
bool verify(String key, List<int> body, List<int> signature);
}
class HS256Algorithm extends JWTAlgorithm {
const HS256Algorithm();
@override
String get name => 'HS256';
@override
List<int> sign(String key, List<int> body) {
final hmac = Hmac(sha256, utf8.encode(key));
return hmac.convert(body).bytes;
}
@override
bool verify(String key, List<int> body, List<int> signature) {
final actual = sign(key, body);
if (actual.length != signature.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] != signature[i]) return false;
}
return true;
}
}

13
lib/src/errors.dart Normal file
View File

@ -0,0 +1,13 @@
class JWTError extends Error {
JWTError(this.message);
final String message;
}
class JWTInvalidError extends JWTError {
JWTInvalidError(String message) : super(message);
}
class JWTExpiredError extends JWTError {
JWTExpiredError() : super('jwt expired');
}

View File

@ -1,90 +0,0 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class JWT {
JWT({
this.payload = const {},
this.audience,
this.subject,
this.issuer,
});
static String secretKey = null;
final _jsonToBase64Url = json.fuse(utf8.fuse(base64Url));
final Map<String, dynamic> payload;
final String audience;
final String subject;
final String issuer;
String signedToken;
String sign({
String key,
Duration expiresIn = null,
bool noTimestamp = false,
}) {
if (key == null && JWT.secretKey != null) key = JWT.secretKey;
assert(key != null);
final header = {'alg': 'HS256', 'typ': 'JWT'};
final algorithm = HS256Algorithm(key);
// Creation timestamp
if (!noTimestamp)
payload['iat'] = DateTime.now();
else
payload.remove('iat');
// Expiration timestamp
if (expiresIn != null)
payload['exp'] = DateTime.now().add(expiresIn);
else
payload.remove('exp');
final body = _jsonToBase64Url.encode(header) + '.' + _jsonToBase64Url.encode(payload);
final signature = base64Url.encode(algorithm.sign(utf8.encode(body)));
return (signedToken = (body + '.' + signature));
}
static JWT verify(String token) {}
}
abstract class JWTAlgorithm {
const JWTAlgorithm();
String get name;
List<int> sign(List<int> body);
bool verify(List<int> body, List<int> signature);
}
class HS256Algorithm extends JWTAlgorithm {
const HS256Algorithm(this.secretKey);
final String secretKey;
@override
String get name => 'HS256';
@override
List<int> sign(List<int> body) {
final hmac = Hmac(sha256, utf8.encode(secretKey));
return hmac.convert(body).bytes;
}
@override
bool verify(List<int> body, List<int> signature) {
final actual = sign(body);
if (actual.length != signature.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] != signature[i]) return false;
}
return true;
}
}

80
lib/src/jwt.dart Normal file
View File

@ -0,0 +1,80 @@
import 'dart:convert';
import 'package:jsonwebtoken/jsonwebtoken.dart';
import './utils.dart';
class JWT {
static JWT verify(String token, String key) {
try {
final parts = token.split('.');
final header = Map<String, dynamic>.from(jsonBase64.decode(base64Padded(parts[0])));
if (header['typ'] != 'JWT') throw JWTInvalidError('not a jwt');
final algorithm = JWTAlgorithm.fromName(header['alg']);
if (parts.length < 3) throw JWTInvalidError('jwt malformated');
final body = utf8.encode(parts[0] + '.' + parts[1]);
final signature = base64Url.decode(base64Padded(parts[2]));
if (!algorithm.verify(key, body, 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 catch (ex) {
throw ex;
} catch (ex) {
throw JWTInvalidError('jwt invalid');
}
}
JWT({
this.payload = const {},
this.audience,
this.subject,
this.issuer,
});
final Map<String, dynamic> payload;
final String audience;
final String subject;
final String issuer;
String sign(
String key, {
JWTAlgorithm algorithm = JWTAlgorithm.HS256,
Duration expiresIn = null,
bool noTimestamp = false,
}) {
final header = {'alg': algorithm.name, 'typ': 'JWT'};
if (!noTimestamp) payload['iat'] = secondsSinceEpoch(DateTime.now());
if (expiresIn != null) payload['exp'] = secondsSinceEpoch(DateTime.now().add(expiresIn));
if (audience != null) payload['aud'] = audience;
if (subject != null) payload['sub'] = subject;
if (issuer != null) payload['iss'] = issuer;
final body = base64Unpadded(jsonBase64.encode(header)) + '.' + base64Unpadded(jsonBase64.encode(payload));
final signature = base64Unpadded(base64Url.encode(algorithm.sign(key, utf8.encode(body))));
return body + '.' + signature;
}
}

26
lib/src/utils.dart Normal file
View File

@ -0,0 +1,26 @@
import 'dart:convert';
final jsonBase64 = json.fuse(utf8.fuse(base64Url));
String base64Unpadded(String value) {
if (value.endsWith('==')) return value.substring(0, value.length - 2);
if (value.endsWith('=')) return value.substring(0, value.length - 1);
return value;
}
String base64Padded(String value) {
final lenght = value.length;
switch (lenght % 4) {
case 2:
return value.padRight(lenght + 2, '=');
case 3:
return value.padRight(lenght + 1, '=');
default:
return value;
}
}
int secondsSinceEpoch(DateTime time) {
return time.millisecondsSinceEpoch ~/ 1000;
}

View File

@ -1,7 +1,8 @@
name: jsonwebtoken
description: A starting point for Dart libraries or applications.
version: 1.0.0
homepage: https://www.example.com
description: A dart implementation of JSON Web Tokens.
version: 0.1.0
repository: https://github.com/jonasroussel/jsonwebtoken
homepage: https://github.com/jonasroussel/jsonwebtoken#readme
environment:
sdk: '>=2.7.0 <3.0.0'