Skip to content

Commit

Permalink
Merge pull request #63 from Bdaya-Dev/fix/refresh-the-cached-token
Browse files Browse the repository at this point in the history
fix: Don't remove the cached tokens when they expire and instead attempt a refresh
  • Loading branch information
ahmednfwela authored Mar 20, 2024
2 parents 9649ee0 + b8d442e commit b34d5f6
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 136 deletions.
8 changes: 5 additions & 3 deletions docs/oidc-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,15 @@ this is used for validating the JWT signature.
### Initialization
After constructing the manager with the proper settings, you MUST call the `manager.init()` function, which will do the following:
1. If the function has already been initialized (checked via the hasInit variable), it simply returns. This is because certain configurations and setups do not need, and should not be, repeated.
1. If the function has already been initialized (checked via the hasInit variable), it simply returns. This is because certain configurations and setups do not need to be repeated.
2. initialize the passed store (calls `OidcStore.init()`)
3. ensure that the discovery document has been retrieved (if the lazy constructor was used).
4. if the discovery document contains a `jwks_uri` adds it the to the keystore.
5. handle various state management tasks. It loads logout requests and state results (from samePage redirects), also attempts to load cached tokens if there are no state results or logout requests.
6. Starts Listening to incoming Front Channel Logout requests.
7. Starts listening to token expiry events for automatic refresh_token circulation.
6. If the loaded token has expired and a refresh token exists, it attempts to refresh it, otherwise the token will be removed form the store.
7. Starts Listening to incoming Front Channel Logout requests.
8. Starts listening to token expiry events for automatic `refresh_token` circulation.
### Login
Expand Down
4 changes: 3 additions & 1 deletion packages/oidc/example/lib/access_token_usage_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import 'package:oidc/oidc.dart';

const kAuthorizationHeader = 'Authorization';
void _tryAppendAccessToken(
OidcUserManager userManager, Map<String, dynamic> headers) {
OidcUserManager userManager,
Map<String, dynamic> headers,
) {
if (headers.containsKey(kAuthorizationHeader)) {
// do nothing if header already exists.
return;
Expand Down
4 changes: 3 additions & 1 deletion packages/oidc/example/lib/pages/secret_page.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
Expand Down Expand Up @@ -199,7 +201,7 @@ class _SecretPageState extends State<SecretPage> {
const Divider(),
Text('id token: ${user.idToken}'),
const Divider(),
Text('token: ${user.token.toJson()}'),
Text('token: ${jsonEncode(user.token.toJson())}'),
],
),
),
Expand Down
81 changes: 62 additions & 19 deletions packages/oidc/lib/src/managers/user_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ class OidcUserManager {
required String? nonce,
required Map<String, dynamic>? attributes,
required OidcProviderMetadata metadata,
bool validateAndSave = true,
}) async {
final currentUser = this.currentUser;
OidcUser newUser;
Expand All @@ -559,7 +560,11 @@ class OidcUserManager {
'Server returned a wrong id_token nonce, might be a replay attack.',
);
}
return _validateAndSaveUser(user: newUser, metadata: metadata);
if (validateAndSave) {
return _validateAndSaveUser(user: newUser, metadata: metadata);
} else {
return newUser;
}
}

Future<void> _saveUser(OidcUser user) async {
Expand Down Expand Up @@ -665,14 +670,15 @@ class OidcUserManager {
),
);
return _createUserFromToken(
token: OidcToken.fromResponse(
tokenResponse,
overrideExpiresIn: settings.getExpiresIn?.call(tokenResponse),
sessionState: currentUser?.token.sessionState,
),
nonce: null,
attributes: null,
metadata: discoveryDocument);
token: OidcToken.fromResponse(
tokenResponse,
overrideExpiresIn: settings.getExpiresIn?.call(tokenResponse),
sessionState: currentUser?.token.sessionState,
),
nonce: null,
attributes: null,
metadata: discoveryDocument,
);
}

Future<void> _listenToTokenRefreshIfSupported(
Expand Down Expand Up @@ -741,29 +747,38 @@ class OidcUserManager {
forgetUser();
}

/// This function validates that a user claims
Future<OidcUser?> _validateAndSaveUser({
List<Exception> _validateUser({
required OidcUser user,
required OidcProviderMetadata metadata,
}) async {
var actualUser = user;
}) {
final errors = <Exception>[
...actualUser.parsedIdToken.claims.validate(
...user.parsedIdToken.claims.validate(
clientId: clientCredentials.clientId,
issuer: metadata.issuer,
expiryTolerance: settings.expiryTolerance,
),
];
if (actualUser.parsedIdToken.claims.subject == null) {
if (user.parsedIdToken.claims.subject == null) {
errors.add(
JoseException('id token is missing a `sub` claim.'),
);
}
if (actualUser.parsedIdToken.claims.issuedAt == null) {
if (user.parsedIdToken.claims.issuedAt == null) {
errors.add(
JoseException('id token is missing an `iat` claim.'),
);
}

return errors;
}

/// This function validates that a user claims
Future<OidcUser?> _validateAndSaveUser({
required OidcUser user,
required OidcProviderMetadata metadata,
}) async {
var actualUser = user;
final errors = _validateUser(user: actualUser, metadata: metadata);
OidcUserInfoResponse? userInfoResp;

if (errors.isEmpty) {
Expand Down Expand Up @@ -804,7 +819,7 @@ class OidcUserManager {
} else {
for (final element in errors) {
_logger.warning(
'found a JWT, but failed the validation test: $element',
'Found a JWT, but failed the validation test: $element',
element,
StackTrace.current,
);
Expand Down Expand Up @@ -931,19 +946,47 @@ class OidcUserManager {
if (rawToken == null) {
return;
}

try {
final decodedAttributes = rawAttributes == null
? null
: jsonDecode(rawAttributes) as Map<String, dynamic>;
final decodedToken = jsonDecode(rawToken) as Map<String, dynamic>;
final token = OidcToken.fromJson(decodedToken);
await _createUserFromToken(
final metadata = discoveryDocument;
var loadedUser = await _createUserFromToken(
token: token,
// nonce is only checked for new tokens.
nonce: null,
attributes: decodedAttributes,
metadata: discoveryDocument,
metadata: metadata,
validateAndSave: false,
);
if (loadedUser != null) {
final validationErrors = _validateUser(
user: loadedUser,
metadata: metadata,
);
final idTokenNeedsRefresh = validationErrors
.whereType<JoseException>()
.any((element) => element.message.startsWith('JWT expired'));
if (token.refreshToken != null &&
(idTokenNeedsRefresh || token.isAccessTokenExpired())) {
loadedUser =
await refreshToken(overrideRefreshToken: token.refreshToken);
}
if (loadedUser != null) {
loadedUser = await _validateAndSaveUser(
user: loadedUser,
metadata: metadata,
);
}
}

if (loadedUser == null) {
_logAndThrow(
'Found a cached token, but the user could not be created or validated');
}
} catch (e) {
// remove invalid tokens, so that they don't get used again.
await store.removeMany(
Expand Down
3 changes: 2 additions & 1 deletion packages/oidc/lib/src/models/event.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:clock/clock.dart';
import 'package:oidc_core/oidc_core.dart';

/// Represents an arbitrary event.
Expand All @@ -8,7 +9,7 @@ abstract class OidcEvent {
});

/// Creates an event whose [at] is now.
OidcEvent.now() : at = DateTime.now();
OidcEvent.now() : at = clock.now();

/// when the event occurred.
final DateTime at;
Expand Down
3 changes: 2 additions & 1 deletion packages/oidc/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ dependencies:
oidc_windows: ^0.3.1+1
# ====================
http: ^1.1.0
jose_plus: ^0.4.3
jose_plus: ^0.4.4
retry: ^3.1.2
rxdart: ^0.27.7
logging: ^1.2.0
json_annotation: ^4.8.1
nonce: ^1.2.0
uuid: ^4.0.0
clock: ^1.1.1

dev_dependencies:
build_runner: ^2.4.6
Expand Down
Loading

0 comments on commit b34d5f6

Please sign in to comment.