Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWT Token decoding and Refresh Token expiration checks #118

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/access_token_response.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:oauth2_client/oauth2_response.dart';

/// Represents the response to an Access Token Request.
Expand Down Expand Up @@ -79,6 +80,22 @@ class AccessTokenResponse extends OAuth2Response {
return expired;
}

bool isRefreshTokenExpired() {
if (!hasRefreshToken()) return true;

final decodedRefreshToken = JwtDecoder.tryDecode(refreshToken ?? '');

if (decodedRefreshToken != null) {
final int? expirationTimestamp = decodedRefreshToken.containsKey('exp') ? decodedRefreshToken['exp'] : null;
if (expirationTimestamp != null) {
final refreshTokenExpirationDate = DateTime.fromMillisecondsSinceEpoch(Duration(seconds: expirationTimestamp).inMilliseconds);
return refreshTokenExpirationDate.difference(DateTime.now()).inSeconds < 0;
}
}

return false;
}

///Checks if the access token must be refreeshed
bool refreshNeeded({secondsToExpiration = 30}) {
var needsRefresh = false;
Expand Down Expand Up @@ -106,6 +123,14 @@ class AccessTokenResponse extends OAuth2Response {
return isValid() ? respMap['access_token'] : null;
}

Map<String, dynamic>? get decodedAccessToken {
final rawAccessToken = accessToken;
if (rawAccessToken != null) {
return JwtDecoder.tryDecode(rawAccessToken);
}
return null;
}

String? get tokenType {
//Some providers (e.g. Slack) don't return the token_type parameter, even if it's required...
//In those cases, fallback to "bearer"
Expand Down
9 changes: 7 additions & 2 deletions lib/oauth2_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ class OAuth2Client {
}

/// Refreshes an Access Token issuing a refresh_token grant to the OAuth2 server.
Future<AccessTokenResponse> refreshToken(String refreshToken,
Future<AccessTokenResponse?> refreshToken(String refreshToken,
{httpClient, required String clientId, String? clientSecret}) async {
final Map params = getRefreshUrlParams(refreshToken: refreshToken);

Expand All @@ -271,7 +271,12 @@ class OAuth2Client {
params: params,
httpClient: httpClient);

return http2TokenResponse(response);
if (response.statusCode >= 200 && response.statusCode < 300) {
return http2TokenResponse(response);
}

return null;

}

/// Revokes both the Access and the Refresh tokens in the provided [tknResp]
Expand Down
3 changes: 2 additions & 1 deletion lib/oauth2_helper.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:oauth2_client/access_token_response.dart';
import 'package:oauth2_client/oauth2_exception.dart';
import 'package:oauth2_client/oauth2_client.dart';
Expand Down Expand Up @@ -88,7 +89,7 @@ class OAuth2Helper {
if (tknResp != null) {
if (tknResp.refreshNeeded()) {
//The access token is expired
if (tknResp.refreshToken != null) {
if (tknResp.refreshToken != null && !tknResp.isRefreshTokenExpired()) {
tknResp = await refreshToken(tknResp.refreshToken!);
} else {
//No refresh token, fetch a new token
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies:
http: ^0.13.4
meta: ^1.7.0
random_string: ^2.3.1
jwt_decoder: ^2.0.1

dev_dependencies:
flutter_test:
Expand Down
6 changes: 3 additions & 3 deletions test/oauth2_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ void main() {
.captured[1],
{'Authorization': 'Basic bXljbGllbnRpZDp0ZXN0X3NlY3JldA=='});

expect(resp.isValid(), true);
expect(resp.accessToken, accessToken);
expect(resp?.isValid() ?? false, true);
expect(resp?.accessToken ?? '', accessToken);
});

test('Error in refreshing token', () async {
Expand All @@ -381,7 +381,7 @@ void main() {
.captured[1],
{'Authorization': 'Basic bXljbGllbnRpZDp0ZXN0X3NlY3JldA=='});

expect(resp.isValid(), false);
expect(resp?.isValid() ?? false, false);
});

test('Authorization url params (1/5)', () {
Expand Down