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

Emit a TokenRefreshFailedEvent when the automatic token refresh fails #153

Open
wants to merge 2 commits into
base: main
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
4 changes: 2 additions & 2 deletions docs/oidc-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ This can be done by calling `logout` with the following optional parameters:

### Listening to currentUser changes

Whenever a user logs in, logs out, or a token gets refreshed automatically, an event is added to the `userChanges()` stream.
Whenever a user logs in, logs out, a token gets refreshed automatically, or the automatic refresh of the token fails, an event is added to the `userChanges()` stream.

This is similar to firebase auth, and can be used to track the current session.

Expand Down Expand Up @@ -395,4 +395,4 @@ static Stream<OidcFrontChannelLogoutIncomingRequest> listenToFrontChannelLogoutR
[http_image]: https://img.shields.io/badge/package-http-0175C2?logo=dart&logoColor=white

[jose_plus_link]: https://pub.dev/packages/jose_plus#create-a-jwt
[jose_plus_image]: https://img.shields.io/badge/package-jose__plus-0175C2?logo=dart&logoColor=white
[jose_plus_image]: https://img.shields.io/badge/package-jose__plus-0175C2?logo=dart&logoColor=white
6 changes: 5 additions & 1 deletion docs/oidc_core.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ The base class for all events, this contains an `at` property that stores when t

Occurs before a user is forgotten, either via `forgetUser()` or via `logout()`.

### OidcTokenRefreshFailedEvent

Occurs when the automatic refresh of a token fails.

## OidcPkcePair

you can use this to generate PKCE key pairs.
Expand Down Expand Up @@ -167,4 +171,4 @@ Contains methods that help you implement the OIDC spec yourself.
---

[package_link]: https://pub.dev/packages/oidc_core
[package_image]: https://img.shields.io/badge/package-oidc__core-0175C2?logo=dart&logoColor=white
[package_image]: https://img.shields.io/badge/package-oidc__core-0175C2?logo=dart&logoColor=white
63 changes: 21 additions & 42 deletions packages/oidc_core/lib/src/managers/user_manager_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,21 @@ abstract class OidcUserManagerBase {
Future<OidcUser?> refreshToken({
String? overrideRefreshToken,
OidcProviderMetadata? discoveryDocumentOverride,
}) {
return doRefreshToken(
overrideRefreshToken: overrideRefreshToken,
discoveryDocumentOverride: discoveryDocumentOverride,
);
}

@protected
Future<OidcUser?> doRefreshToken({
OidcToken? overrideToken,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the overrideToken is unnecessary here, as you said, shared code should go into a doRefreshToken method that's marked @protected

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions, should be done.

String? overrideRefreshToken,
OidcProviderMetadata? discoveryDocumentOverride,
}) async {
ensureInit();
final token = overrideToken ?? currentUser?.token;
final discoveryDocument =
discoveryDocumentOverride ?? this.discoveryDocument;
if (!discoveryDocument.grantTypesSupportedOrDefault
Expand All @@ -743,8 +756,7 @@ abstract class OidcUserManagerBase {
return null;
}

final refreshToken =
overrideRefreshToken ?? currentUser?.token.refreshToken;
final refreshToken = overrideRefreshToken ?? token?.refreshToken;
if (refreshToken == null) {
// Can't refresh the access token anyway.
return null;
Expand All @@ -767,7 +779,7 @@ abstract class OidcUserManagerBase {
token: OidcToken.fromResponse(
tokenResponse,
overrideExpiresIn: settings.getExpiresIn?.call(tokenResponse),
sessionState: currentUser?.token.sessionState,
sessionState: token?.sessionState,
),
nonce: null,
userInfo: null,
Expand Down Expand Up @@ -798,48 +810,15 @@ abstract class OidcUserManagerBase {
OidcTokenExpiringEvent.now(currentToken: event),
);

if (!discoveryDocument.grantTypesSupportedOrDefault
.contains(OidcConstants_GrantType.refreshToken)) {
//Server doesn't support refresh_token grant.
return;
}

final refreshToken = event.refreshToken;
if (refreshToken == null) {
return;
}
OidcUser? newUser;
//try getting a new token.
try {
final tokenResponse = await OidcEndpoints.token(
tokenEndpoint: discoveryDocument.tokenEndpoint!,
credentials: clientCredentials,
client: httpClient,
headers: settings.extraTokenHeaders,
request: OidcTokenRequest.refreshToken(
refreshToken: refreshToken,
clientId: clientCredentials.clientId,
clientSecret: clientCredentials.clientSecret,
extra: settings.extraTokenParameters,
scope: settings.scope,
),
);
newUser = await createUserFromToken(
token: OidcToken.fromResponse(
tokenResponse,
overrideExpiresIn: settings.getExpiresIn?.call(tokenResponse),
sessionState: event.sessionState,
),
nonce: null,
attributes: null,
userInfo: null,
metadata: discoveryDocument,
);
} catch (e) {
//swallow errors on fail, but unload the event manager.
final newUser = await doRefreshToken(overrideToken: event);
logger.fine('Refreshed a token and got a new user: ${newUser?.uid}');
} catch (error) {
tokenEvents.unload();
eventsController.add(
OidcTokenRefreshFailedEvent.now(error: error),
);
}
logger.fine('Refreshed a token and got a new user: ${newUser?.uid}');
}

@protected
Expand Down
1 change: 1 addition & 0 deletions packages/oidc_core/lib/src/models/events/_exports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export 'event.dart';
export 'pre_logout_event.dart';
export 'token_expired_event.dart';
export 'token_expiring_event.dart';
export 'token_refresh_failed_event.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:oidc_core/oidc_core.dart';

/// An event that gets raised when the auto token refresh fails.
class OidcTokenRefreshFailedEvent extends OidcEvent {
///
const OidcTokenRefreshFailedEvent({
required this.error,
required super.at,
});

///
OidcTokenRefreshFailedEvent.now({
required this.error,
}) : super.now();

final Object error;
}