Skip to content

Commit

Permalink
Revoke sessions (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira authored Nov 17, 2024
1 parent 2af4f10 commit 9d867ee
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 14 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ active session and clear it from the session manager:

```swift
guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
try await Descope.auth.logout(refreshJwt: refreshJwt)
try await Descope.auth.revokeSessions(.currentSession, refreshJwt: refreshJwt)
Descope.sessionManager.clearSession()
```

Expand Down Expand Up @@ -360,7 +360,7 @@ you configured in the Descope console earlier.
```swift
do {
showLoading(true)
let authResponse = try await Descope.passkey.native(provider: .apple, options: [])
let authResponse = try await Descope.passkey.signUpOrIn(loginId: "[email protected]", options: [])
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
showHomeScreen()
Expand Down
11 changes: 9 additions & 2 deletions src/internal/http/DescopeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,13 @@ final class DescopeClient: HTTPClient, @unchecked Sendable {
return try await post("auth/refresh", headers: authorization(with: refreshJwt))
}

func logout(refreshJwt: String) async throws {
try await post("auth/logout", headers: authorization(with: refreshJwt))
func logout(type: RevokeType, refreshJwt: String) async throws {
switch type {
case .currentSession:
try await post("auth/logout", headers: authorization(with: refreshJwt))
case .allSessions:
try await post("auth/logoutall", headers: authorization(with: refreshJwt))
}
}

// MARK: - Shared
Expand Down Expand Up @@ -478,12 +483,14 @@ final class DescopeClient: HTTPClient, @unchecked Sendable {
var stepup: Bool = false
var mfa: Bool = false
var customClaims: [String: Any] = [:]
var revokeOtherSessions = false

var dictValue: [String: Any?] {
return [
"stepup": stepup ? true : nil,
"mfa": mfa ? true : nil,
"customClaims": customClaims.isEmpty ? nil : customClaims,
"revokeOtherSessions": revokeOtherSessions ? true : nil,
]
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/internal/others/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public extension DescopeSSO {
}
}

public extension DescopeAuth {
@available(*, deprecated, message: "Call revokeSessions(.currentSession, refreshJwt: refreshJwt) instead")
func logout(refreshJwt: String) async throws {
return try await revokeSessions(.currentSession, refreshJwt: refreshJwt)
}
}

public extension DescopeSDK {
@available(*, deprecated, message: "Use the DescopeSDK.init(projectId:with:) initializer instead")
convenience init(config: DescopeConfig) {
Expand Down
4 changes: 2 additions & 2 deletions src/internal/routes/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class Auth: DescopeAuth {
return try await client.refresh(refreshJwt: refreshJwt).convert()
}

func logout(refreshJwt: String) async throws {
try await client.logout(refreshJwt: refreshJwt)
func revokeSessions(_ revoke: RevokeType, refreshJwt: String) async throws {
try await client.logout(type: revoke, refreshJwt: refreshJwt)
}
}
2 changes: 2 additions & 0 deletions src/internal/routes/Shared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ extension [SignInOptions] {
case .mfa(let value):
loginOptions.mfa = true
refreshJwt = value
case .revokeOtherSessions:
loginOptions.revokeOtherSessions = true
}
}
return (refreshJwt, loginOptions)
Expand Down
33 changes: 29 additions & 4 deletions src/sdk/Callbacks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,38 @@ public extension DescopeAuth {
}
}

/// Logs out from an active ``DescopeSession``.
/// Revokes active sessions for the user.
///
/// - Parameter refreshJwt: the `refreshJwt` from an active ``DescopeSession``.
func logout(refreshJwt: String, completion: @escaping @Sendable (Result<Void, Error>) -> Void) {
/// It's a good security practice to remove refresh JWTs from the Descope servers if
/// they become redundant before expiry. This function will usually be called with `.currentSession`
/// when the user wants to sign out of the application. For example:
///
/// ```swift
/// func didPressSignOut() {
/// guard let session = Descope.sessionManager.session else { return }
///
/// // 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
/// Descope.sessionManager.clearSession()
/// Task {
/// try? await Descope.auth.revokeSessions(.currentSession, refreshJwt: session.refreshJwt)
/// }
///
/// showLaunchScreen()
/// }
/// ```
///
/// - Important: When called with `.allSessions` the provided refresh JWT will not
/// be usable anymore and the user will need to sign in again.
///
/// - Parameters:
/// - revoke: Pass `.currentSession` to revoke the session in the `refreshJwt`
/// parameter or see ``RevokeType`` for more options.
/// - refreshJwt: The `refreshJwt` from an active ``DescopeSession``.
func revokeSessions(_ revoke: RevokeType, refreshJwt: String, completion: @escaping @Sendable (Result<Void, Error>) -> Void) {
Task {
do {
completion(.success(try await logout(refreshJwt: refreshJwt)))
completion(.success(try await revokeSessions(revoke, refreshJwt: refreshJwt)))
} catch {
completion(.failure(error))
}
Expand Down
31 changes: 28 additions & 3 deletions src/sdk/Routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,35 @@ public protocol DescopeAuth: Sendable {
/// - Returns: A new ``RefreshResponse`` with a refreshed `sessionJwt`.
func refreshSession(refreshJwt: String) async throws -> RefreshResponse

/// Logs out from an active ``DescopeSession``.
/// Revokes active sessions for the user.
///
/// - Parameter refreshJwt: the `refreshJwt` from an active ``DescopeSession``.
func logout(refreshJwt: String) async throws
/// It's a good security practice to remove refresh JWTs from the Descope servers if
/// they become redundant before expiry. This function will usually be called with `.currentSession`
/// when the user wants to sign out of the application. For example:
///
/// ```swift
/// func didPressSignOut() {
/// guard let session = Descope.sessionManager.session else { return }
///
/// // 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
/// Descope.sessionManager.clearSession()
/// Task {
/// try? await Descope.auth.revokeSessions(.currentSession, refreshJwt: session.refreshJwt)
/// }
///
/// showLaunchScreen()
/// }
/// ```
///
/// - Important: When called with `.allSessions` the provided refresh JWT will not
/// be usable anymore and the user will need to sign in again.
///
/// - Parameters:
/// - revoke: Pass `.currentSession` to revoke the session in the `refreshJwt`
/// parameter or see ``RevokeType`` for more options.
/// - refreshJwt: The `refreshJwt` from an active ``DescopeSession``.
func revokeSessions(_ revoke: RevokeType, refreshJwt: String) async throws
}


Expand Down
2 changes: 1 addition & 1 deletion src/session/Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import Foundation
/// session and clear it from the session manager:
///
/// guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
/// try await Descope.auth.logout(refreshJwt: refreshJwt)
/// try await Descope.auth.revokeSessions(.currentSession, refreshJwt: refreshJwt)
/// Descope.sessionManager.clearSession()
///
/// You can customize how the ``DescopeSessionManager`` behaves by using your own
Expand Down
15 changes: 15 additions & 0 deletions src/types/Others.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ public enum DeliveryMethod: String, Sendable {
case email
}

/// Which sessions are revoked when calling ``DescopeAuth/revokeSessions(_:refreshJwt:)``.
public enum RevokeType: Sendable {
/// Revoke the session for the provided refresh JWT.
case currentSession

/// Revoke all sessions for the user, including the session for the provided refresh JWT.
case allSessions
}

/// The provider to use in an OAuth flow.
public struct OAuthProvider: Sendable, ExpressibleByStringLiteral {
public static let facebook: OAuthProvider = "facebook"
Expand Down Expand Up @@ -90,6 +99,12 @@ public enum SignInOptions: @unchecked Sendable {
/// After the MFA authentication completes successfully the `amr` claim in both the session
/// and refresh JWTs will be an array with an entry for each authentication method used.
case mfa(refreshJwt: String)

/// Revoke all other active sessions for the user.
///
/// Use this option to ensure the user only ever has one active sign in at a time, and that
/// refresh JWTs from previous sign ins or in other devices are revoked.
case revokeOtherSessions
}

/// Used to configure how users are updated.
Expand Down

0 comments on commit 9d867ee

Please sign in to comment.