diff --git a/README.md b/README.md index dd9a5e9..8ef2c1d 100644 --- a/README.md +++ b/README.md @@ -160,13 +160,19 @@ When the user wants to sign out of the application we revoke the active session and clear it from the session manager: ```dart -final refreshJwt = Descope.sessionManager.session?.refreshJwt; +final refreshJwt = Descope.sessionManager.session?.refreshToken.jwt; if (refreshJwt != null) { - Descope.auth.logout(refreshJwt); Descope.sessionManager.clearSession(); + try { + Descope.auth.revokeSessions(RevokeType.currentSession, refreshJwt); + } catch (e) { + // handle errors + } } ``` +It is also possible to revoke all sessions by providing the appropriate `RevokeType` parameter. + You can customize how the `DescopeSessionManager` behaves by using your own `storage` and `lifecycle` objects. See the documentation for more details. diff --git a/lib/src/internal/http/descope_client.dart b/lib/src/internal/http/descope_client.dart index 5b89c9c..7f4bba7 100644 --- a/lib/src/internal/http/descope_client.dart +++ b/lib/src/internal/http/descope_client.dart @@ -344,8 +344,9 @@ class DescopeClient extends HttpClient { return post('auth/refresh', JWTServerResponse.decoder, headers: authorization(refreshJwt)); } - Future logout(String refreshJwt) { - return post('auth/logout', emptyResponse, headers: authorization(refreshJwt)); + Future logout(RevokeType revokeType, String refreshJwt) { + final route = revokeType == RevokeType.currentSession ? 'auth/logout' : 'auth/logoutall'; + return post(route, emptyResponse, headers: authorization(refreshJwt)); } // Internal @@ -384,6 +385,7 @@ extension on SignInOptions { 'stepup': stepupRefreshJwt != null ? true : null, 'mfa': mfaRefreshJwt != null ? true : null, 'customClaims': customClaims.isNotEmpty ? customClaims : null, + 'revokeOtherSessions': revokeOtherSessions, }; } } diff --git a/lib/src/internal/routes/auth.dart b/lib/src/internal/routes/auth.dart index 79a66e6..689104c 100644 --- a/lib/src/internal/routes/auth.dart +++ b/lib/src/internal/routes/auth.dart @@ -1,7 +1,8 @@ +import '/src/internal/http/descope_client.dart'; import '/src/sdk/routes.dart'; +import '/src/types/others.dart'; import '/src/types/responses.dart'; import '/src/types/user.dart'; -import '../http/descope_client.dart'; import 'shared.dart'; class Auth implements DescopeAuth { @@ -19,8 +20,15 @@ class Auth implements DescopeAuth { return (await client.refresh(refreshJwt)).toRefreshResponse(); } + @override + Future revokeSessions(RevokeType revokeType, String refreshJwt) async { + return client.logout(revokeType, refreshJwt); + } + + // Deprecated + @override Future logout(String refreshJwt) { - return client.logout(refreshJwt); + return revokeSessions(RevokeType.currentSession, refreshJwt); } } diff --git a/lib/src/sdk/routes.dart b/lib/src/sdk/routes.dart index 4f3b728..25563b4 100644 --- a/lib/src/sdk/routes.dart +++ b/lib/src/sdk/routes.dart @@ -19,7 +19,33 @@ abstract class DescopeAuth { /// or is about expire. Future refreshSession(String refreshJwt); + /// It's a good security practice to remove refresh JWTs from the Descope servers if + /// they become redundant before expiry. This function will called with a [RevokeType], usually [RevokeType.currentSession], + /// and a valid refresh JWT when the user wants to sign out of the application. For example: + /// + /// void logout() { + /// // clear the session locally from the app and spawn a background task to revoke + /// // the refreshJWT from the Descope servers without waiting for the call to finish + /// final refreshJwt = Descope.sessionManager.session?.refreshToken.jwt; + /// if (refreshJwt != null) { + /// Descope.sessionManager.clearSession(); + /// try { + /// Descope.auth.revokeSessions(RevokeType.currentSession, refreshJwt); + /// } catch (e) { + /// // handle errors + /// } + /// showLaunchScreen(); + /// } + /// } + /// + /// - Important: When called with [RevokeType.allSessions] the provided refresh JWT will not + /// be usable anymore and the user will need to sign in again. + Future revokeSessions(RevokeType revokeType, String refreshJwt); + + // Deprecated + /// Logs out from an active [DescopeSession]. + @Deprecated('Use revokeSessions instead') Future logout(String refreshJwt); } diff --git a/lib/src/types/others.dart b/lib/src/types/others.dart index d78a6f1..3232e82 100644 --- a/lib/src/types/others.dart +++ b/lib/src/types/others.dart @@ -1,3 +1,14 @@ +/// Which sessions to revoke when calling `DescopeAuth.revokeSessions()` +enum RevokeType { + /// Revokes the provided refresh JWT. + currentSession, + /// Revokes the provided refresh JWT and all other active sessions for the user. + /// + /// - Important: This causes all sessions for the user to be removed, and the provided + /// refresh JWT will not be usable after the logout call completes. + allSessions, +} + /// The delivery method for an OTP or Magic Link message. enum DeliveryMethod { email, @@ -75,7 +86,10 @@ class SignInOptions { /// be nested under the `nsec` custom claim. final Map customClaims; - const SignInOptions({this.stepupRefreshJwt, this.mfaRefreshJwt, this.customClaims = const {}}); + /// Revokes all other active sessions for the user besides the new session being created. + final bool revokeOtherSessions; + + const SignInOptions({this.stepupRefreshJwt, this.mfaRefreshJwt, this.customClaims = const {}, this.revokeOtherSessions = false}); } /// Used to configure how users are updated.