Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4938e22
Add support for MRRT to the Credentials Manager
Widcket Feb 8, 2025
57d5547
Rename internal key params
Widcket Feb 10, 2025
51181ef
Use a separate `APICredentials` model and methods
Widcket Feb 12, 2025
21fe14a
Update the ID and refresh tokens of the stored app credentials
Widcket Feb 13, 2025
477142b
Remove `exchangeFailed` error
Widcket Feb 14, 2025
8cfc3ee
Allow to force renew API credentials by requesting different scopes
Widcket Feb 18, 2025
bde5ca9
Make `scope` optional
Widcket Feb 27, 2025
778ad02
Remove empty file
Widcket Feb 27, 2025
f99182a
Update network stubs in API client tests
Widcket Feb 27, 2025
19d6778
Merge branch 'poc/mrrt' into feat/mrrt
Widcket Mar 4, 2025
a89b042
Add API docs
Widcket Mar 4, 2025
dac40f0
Make `store(apiCredentials:forAudience:)` internal
Widcket Mar 5, 2025
e5c7b68
Update API docs
Widcket Mar 5, 2025
7a57905
Add `apiExchangeFailed` case to `CredentialsManagerError`
Widcket Mar 5, 2025
21640c1
Fix typos in API docs
Widcket Mar 9, 2025
535d960
Update EXAMPLE.md
Widcket Mar 9, 2025
21f986f
Use correct arg in test response
Widcket Mar 9, 2025
81b9b03
Remove unnecessary matcher
Widcket Mar 9, 2025
e5cb516
Add assert to test
Widcket Mar 9, 2025
08427ad
Use correct assert in test
Widcket Mar 9, 2025
733d2c6
Remove unnecessary matcher
Widcket Mar 9, 2025
d3053c2
Store `APICredentials` expiry as seconds since epoch
Widcket Mar 10, 2025
5190507
Update API docs
Widcket Mar 10, 2025
717d675
Encapsulate encoding/decoding logic inside `APICredentials`
Widcket Mar 10, 2025
e36ece5
Merge branch 'master' into feat/mrrt
Widcket Mar 11, 2025
e8b06bd
Remove duplicated test case
Widcket Mar 11, 2025
398a0af
Merge branch 'feat/mrrt' of github.com:auth0/Auth0.swift into feat/mrrt
Widcket Mar 11, 2025
d120b67
Add `@preconcurrency` to test import
Widcket Mar 11, 2025
f0831da
Capture the credentials manager instance in async/await tests
Widcket Mar 11, 2025
2918945
Increase timeout for `SFSafariViewController` tests
Widcket Mar 11, 2025
0c50ef2
Merge branch 'master' into feat/mrrt
Widcket Mar 12, 2025
2363b23
Merge branch 'master' into feat/mrrt
Widcket Apr 14, 2025
7eac85f
Merge branch 'master' into feat/mrrt
Widcket Apr 15, 2025
97fb086
Update broken API docs URLs
Widcket Apr 15, 2025
774a4c6
Merge branch 'master' into feat/mrrt
Widcket Apr 16, 2025
6ce73bd
Merge branch 'master' into feat/mrrt
Widcket Apr 16, 2025
704e7c1
Update docs page URL for revoke endpoint
Widcket Apr 16, 2025
8fc2547
Merge branch 'feat/mrrt' of github.com:auth0/Auth0.swift into feat/mrrt
Widcket Apr 16, 2025
be17ba0
Merge branch 'master' into feat/mrrt
Widcket Apr 17, 2025
1024f25
Merge branch 'master' into feat/mrrt
Widcket Apr 22, 2025
dae1756
Merge branch 'master' into feat/mrrt
Widcket Apr 23, 2025
6aa3f62
Merge branch 'master' into feat/mrrt
Widcket Apr 29, 2025
a9bc1b9
Fix merge mishap
Widcket Apr 29, 2025
f7d233c
Make `scope` non-optional and use synthetic Codable conformance
Widcket Apr 29, 2025
9d52b5c
Merge branch 'master' into feat/mrrt
Widcket May 5, 2025
5e1be0f
Address TODOs from previously merged PRs
Widcket May 6, 2025
954499b
Merge branch 'master' into feat/mrrt
Widcket May 8, 2025
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
22 changes: 22 additions & 0 deletions Auth0.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@
5CF539292836FB0C0073F623 /* ClearSessionTransactionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF539272836FB0C0073F623 /* ClearSessionTransactionSpec.swift */; };
5CF5392B283835470073F623 /* ASProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF5392A283835460073F623 /* ASProviderSpec.swift */; };
5CF5392C283835470073F623 /* ASProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF5392A283835460073F623 /* ASProviderSpec.swift */; };
5CFB82502D5BF324009FD237 /* APICredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB824F2D5BF31D009FD237 /* APICredentials.swift */; };
5CFB82512D5BF324009FD237 /* APICredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB824F2D5BF31D009FD237 /* APICredentials.swift */; };
5CFB82522D5BF324009FD237 /* APICredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB824F2D5BF31D009FD237 /* APICredentials.swift */; };
5CFB82532D5BF324009FD237 /* APICredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB824F2D5BF31D009FD237 /* APICredentials.swift */; };
5CFB82542D5BF324009FD237 /* APICredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB824F2D5BF31D009FD237 /* APICredentials.swift */; };
5CFB82562D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */; };
5CFB82572D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */; };
5CFB82582D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */; };
5CFB82592D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */; };
5F06DDC91CC66B710011842B /* Auth0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F06DDC81CC66B710011842B /* Auth0.swift */; };
5F06DDCA1CC66B710011842B /* Auth0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F06DDC81CC66B710011842B /* Auth0.swift */; };
5F1FBB9A1D8A44C0006B0B85 /* ResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F1FBB981D8A4465006B0B85 /* ResponseSpec.swift */; };
Expand Down Expand Up @@ -678,6 +687,8 @@
5CF539232836DCC10073F623 /* SafariProviderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariProviderSpec.swift; sourceTree = "<group>"; };
5CF539272836FB0C0073F623 /* ClearSessionTransactionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearSessionTransactionSpec.swift; sourceTree = "<group>"; };
5CF5392A283835460073F623 /* ASProviderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASProviderSpec.swift; sourceTree = "<group>"; };
5CFB824F2D5BF31D009FD237 /* APICredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICredentials.swift; sourceTree = "<group>"; };
5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICredentialsSpec.swift; sourceTree = "<group>"; };
5F049B6E1CB42C29006F6C05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Auth0/Info.plist; sourceTree = "<group>"; };
5F06DD781CC448B10011842B /* Auth0.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Auth0.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5F06DD851CC448C90011842B /* Auth0.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Auth0.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -1188,6 +1199,7 @@
children = (
5FBBF0421CCA90300024D2AF /* AuthenticationSpec.swift */,
5FE2F8A51CCA9C17003628F4 /* CredentialsSpec.swift */,
5CFB82552D5E9F94009FD237 /* APICredentialsSpec.swift */,
5B2860D41EEF20F300C75D54 /* UserInfoSpec.swift */,
5C4F553423C9124200C89615 /* JWKSpec.swift */,
5FD255B01D14A9E000387ECB /* AuthenticationErrorSpec.swift */,
Expand Down Expand Up @@ -1255,6 +1267,7 @@
5C4F552223C8FBA100C89615 /* JWKS.swift */,
5B2860CD1EEAC30500C75D54 /* UserInfo.swift */,
5FDE874E1D8A424700EA27DC /* Credentials.swift */,
5CFB824F2D5BF31D009FD237 /* APICredentials.swift */,
5FDE874F1D8A424700EA27DC /* Handlers.swift */,
);
name = Authentication;
Expand Down Expand Up @@ -2052,6 +2065,7 @@
5FD255B71D14F00900387ECB /* Auth0Error.swift in Sources */,
5F06DDC91CC66B710011842B /* Auth0.swift in Sources */,
5B1748741EF2D3A40060E653 /* Helpers.swift in Sources */,
5CFB82542D5BF324009FD237 /* APICredentials.swift in Sources */,
5FDE87691D8A424700EA27DC /* Credentials.swift in Sources */,
5FE2F8B21CCEAED8003628F4 /* Requestable.swift in Sources */,
5C4F551E23C8FB8E00C89615 /* Array+Encode.swift in Sources */,
Expand Down Expand Up @@ -2088,6 +2102,7 @@
5C0AF09E2833420200162044 /* WebAuthentication.swift in Sources */,
5FDE876A1D8A424700EA27DC /* Credentials.swift in Sources */,
5C354C05276CE1A500ADBC86 /* PasswordlessType.swift in Sources */,
5CFB82512D5BF324009FD237 /* APICredentials.swift in Sources */,
5F28B4621D8216180000EB23 /* Loggable.swift in Sources */,
5C41F6D4244F974100252548 /* OAuth2Grant.swift in Sources */,
5C41F6D5244F974B00252548 /* ClearSessionTransaction.swift in Sources */,
Expand Down Expand Up @@ -2160,6 +2175,7 @@
5CF5391D2836CEC00073F623 /* WebAuthenticationSpec.swift in Sources */,
5FADB60F1CED7E5200D4BB50 /* UserPatchAttributesSpec.swift in Sources */,
5C4F553523C9124200C89615 /* JWKSpec.swift in Sources */,
5CFB82592D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */,
5FA250541D4A85A200C544FA /* WebAuthErrorSpec.swift in Sources */,
5CB41D7623D0C15000074024 /* IDTokenValidatorBaseSpec.swift in Sources */,
5B9262C31ECF0CC200F4F6D3 /* BioAuthenticationSpec.swift in Sources */,
Expand Down Expand Up @@ -2216,6 +2232,7 @@
C177D7762C2BE00D0094C657 /* StubURLProtocol.swift in Sources */,
5CE775AF244FD66D00D054A0 /* OAuth2GrantSpec.swift in Sources */,
5FD255B21D14A9E000387ECB /* AuthenticationErrorSpec.swift in Sources */,
5CFB82582D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */,
5C53A7E92703A23200A7C0A3 /* UserInfoSpec.swift in Sources */,
C177D7712C2BDFE40094C657 /* NetworkStub.swift in Sources */,
5CF539252836DCC10073F623 /* SafariProviderSpec.swift in Sources */,
Expand Down Expand Up @@ -2265,6 +2282,7 @@
5CC9940324ED9EC50027DC74 /* CredentialsManager.swift in Sources */,
5F23E6E41D4ACD8500C3F2D9 /* JSONObjectPayload.swift in Sources */,
5C4F551C23C8FB8E00C89615 /* String+URLSafe.swift in Sources */,
5CFB82502D5BF324009FD237 /* APICredentials.swift in Sources */,
5F23E6DC1D4ACD6100C3F2D9 /* NSData+URLSafe.swift in Sources */,
5F23E6E61D4ACD8500C3F2D9 /* Requestable.swift in Sources */,
5C354C07276CE1A500ADBC86 /* PasswordlessType.swift in Sources */,
Expand Down Expand Up @@ -2304,6 +2322,7 @@
5F23E70D1D4B88FC00C3F2D9 /* JSONObjectPayload.swift in Sources */,
5C4F552623C8FBA100C89615 /* JWKS.swift in Sources */,
5B0893E620F8A52100FBF962 /* CredentialsManager.swift in Sources */,
5CFB82532D5BF324009FD237 /* APICredentials.swift in Sources */,
5F23E7061D4B88EA00C3F2D9 /* NSData+URLSafe.swift in Sources */,
5F23E70F1D4B88FC00C3F2D9 /* Requestable.swift in Sources */,
5C354C06276CE1A500ADBC86 /* PasswordlessType.swift in Sources */,
Expand All @@ -2329,6 +2348,7 @@
5F28B4691D8300D50000EB23 /* LoggerSpec.swift in Sources */,
5F1FBB9A1D8A44C0006B0B85 /* ResponseSpec.swift in Sources */,
5F331B0C1D4BB7F900AE4382 /* UsersSpec.swift in Sources */,
5CFB82572D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */,
C12BFE442C352DD700D1CC00 /* StubURLProtocol.swift in Sources */,
5F331B0E1D4BB80700AE4382 /* Matchers.swift in Sources */,
5F331B0A1D4BB7F900AE4382 /* ManagementSpec.swift in Sources */,
Expand Down Expand Up @@ -2370,6 +2390,7 @@
C1B3B9F12C24B6D4004A32A4 /* PasswordlessType.swift in Sources */,
C1B3B9F22C24B6D4004A32A4 /* Challenge.swift in Sources */,
C1B3B9F32C24B6D4004A32A4 /* JWKS.swift in Sources */,
5CFB82522D5BF324009FD237 /* APICredentials.swift in Sources */,
C1B3B9F42C24B6D4004A32A4 /* UserInfo.swift in Sources */,
C1B3B9F52C24B6D4004A32A4 /* Credentials.swift in Sources */,
C1B3B9F62C24B6D4004A32A4 /* Handlers.swift in Sources */,
Expand Down Expand Up @@ -2455,6 +2476,7 @@
C1B3BA452C24BA36004A32A4 /* IDTokenValidatorMocks.swift in Sources */,
C1B3BA462C24BA36004A32A4 /* IDTokenValidatorSpec.swift in Sources */,
C1B3BA472C24BA36004A32A4 /* IDTokenSignatureValidatorSpec.swift in Sources */,
5CFB82562D5E9F9B009FD237 /* APICredentialsSpec.swift in Sources */,
C1B3BA482C24BA36004A32A4 /* ClaimValidatorsSpec.swift in Sources */,
C1B3BA492C24BA37004A32A4 /* BioAuthenticationSpec.swift in Sources */,
C1B3BA4A2C24BA37004A32A4 /* CredentialsManagerSpec.swift in Sources */,
Expand Down
121 changes: 121 additions & 0 deletions Auth0/APICredentials.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Foundation

private struct _A0APICredentials {
let accessToken: String
let tokenType: String
let expiresIn: Date
let scope: String?
}

/// User's credentials obtained from Auth0 for a specific API as the result of exchanging a refresh token.
public struct APICredentials: CustomStringConvertible {

/// Token that can be used to make authenticated requests to the API.
///
/// ## See Also
///
/// - [Access Tokens](https://auth0.com/docs/secure/tokens/access-tokens)
public let accessToken: String

/// Indicates how the access token should be used. For example, as a bearer token.
public let tokenType: String

/// When the access token expires.
public let expiresIn: Date

/// The scopes that have been granted by Auth0.
///
/// ## See Also
///
/// - [Scopes](https://auth0.com/docs/get-started/apis/scopes)
public let scope: String?

/// Custom description that redacts the access token with `<REDACTED>`.
public var description: String {
let redacted = "<REDACTED>"
let values = _A0APICredentials(accessToken: redacted,
tokenType: self.tokenType,
expiresIn: self.expiresIn,
scope: self.scope)
return String(describing: values).replacingOccurrences(of: "_A0APICredentials", with: "APICredentials")
}

// MARK: - Initializer

/// Default initializer.
public init(accessToken: String,
tokenType: String,
expiresIn: Date,
scope: String? = nil) {
self.accessToken = accessToken
self.tokenType = tokenType
self.expiresIn = expiresIn
self.scope = scope
}
}

// MARK: - Codable

extension APICredentials: Codable {

enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
case scope
}

private static let jsonEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
return encoder
}()

private static let jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
return decoder
}()

internal func encode() throws -> Data {
return try Self.jsonEncoder.encode(self)
}

internal init(from data: Data) throws {
self = try Self.jsonDecoder.decode(Self.self, from: data)
}

/// `Encodable` initializer.
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(accessToken, forKey: .accessToken)
try container.encode(tokenType, forKey: .tokenType)
try container.encode(expiresIn, forKey: .expiresIn)
try container.encodeIfPresent(scope, forKey: .scope)
}

/// `Decodable` initializer.
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)

accessToken = try values.decode(String.self, forKey: .accessToken)
tokenType = try values.decode(String.self, forKey: .tokenType)
expiresIn = try values.decode(Date.self, forKey: .expiresIn)
scope = try values.decodeIfPresent(String.self, forKey: .scope)
}

}

// MARK: - Internal Initializer

extension APICredentials {

init(from credentials: Credentials) {
self.accessToken = credentials.accessToken
self.tokenType = credentials.tokenType
self.expiresIn = credentials.expiresIn
self.scope = credentials.scope
}

}
15 changes: 12 additions & 3 deletions Auth0/Auth0Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,19 +276,28 @@ struct Auth0Authentication: Authentication {
])
}

func renew(withRefreshToken refreshToken: String, scope: String? = nil) -> Request<Credentials, AuthenticationError> {
func renew(withRefreshToken refreshToken: String, audience: String? = nil, scope: String? = nil) -> Request<Credentials, AuthenticationError> {
var payload: [String: Any] = [
"refresh_token": refreshToken,
"grant_type": "refresh_token",
"client_id": self.clientId
]
payload["scope"] = scope
let oauthToken = URL(string: "oauth/token", relativeTo: self.url)!

if let audience = audience {
// Make sure to always include the 'openid' scope if we're trying to get a new set of credentials for an
// API like My Account API. This way, we'll always get an ID token, matching the existing login behavior.
payload["scope"] = includeRequiredScope(in: scope)
payload["audience"] = audience
Comment on lines +430 to +433
Copy link
Contributor Author

@Widcket Widcket Mar 11, 2025

Choose a reason for hiding this comment

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

We're enforcing the openid scope only when exchanging for API-specific credentials.

} else {
payload["scope"] = scope
}

return Request(session: session,
url: oauthToken,
method: "POST",
handle: codable,
parameters: payload, // Initializer does not enforce 'openid' scope
parameters: payload,
logger: self.logger,
telemetry: self.telemetry)
}
Expand Down
15 changes: 9 additions & 6 deletions Auth0/Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -668,19 +668,22 @@ public protocol Authentication: Trackable, Loggable {
}
```

You can get a downscoped access token by requesting fewer scopes than were originally requested on login:
You can request credentials for a specific API by passing its audience value. The default scopes configured for
the API will be granted if you don't request any specific scopes.

```swift
Auth0
.authentication()
.renew(withRefreshToken: credentials.refreshToken,
scope: "openid offline_access")
audience: "http://example.com/api",
scope: "read:todos update:todos")
.start { print($0) }
```

- Parameters:
- refreshToken: The refresh token.
- scope: Space-separated list of scope values to request. Defaults to `nil`, which will ask for the same scopes that were originally requested on login.
- audience: Identifier of the API that your application is requesting access to. Defaults to `nil`.
- scope: Space-separated list of scope values to request. Defaults to `nil`.
- Returns: A request that will yield Auth0 user's credentials.

## See Also
Expand All @@ -689,7 +692,7 @@ public protocol Authentication: Trackable, Loggable {
- [Authentication API Endpoint](https://auth0.com/docs/api/authentication#refresh-token)
- <doc:RefreshTokens>
*/
func renew(withRefreshToken refreshToken: String, scope: String?) -> Request<Credentials, AuthenticationError>
func renew(withRefreshToken refreshToken: String, audience: String?, scope: String?) -> Request<Credentials, AuthenticationError>

/**
Revokes a user's refresh token by performing a request to the `/oauth/revoke` endpoint.
Expand Down Expand Up @@ -788,8 +791,8 @@ public extension Authentication {
return self.startPasswordless(phoneNumber: phoneNumber, type: type, connection: connection)
}

func renew(withRefreshToken refreshToken: String, scope: String? = nil) -> Request<Credentials, AuthenticationError> {
return self.renew(withRefreshToken: refreshToken, scope: scope)
func renew(withRefreshToken refreshToken: String, audience: String? = nil, scope: String? = nil) -> Request<Credentials, AuthenticationError> {
return self.renew(withRefreshToken: refreshToken, audience: audience, scope: scope)
}

}
33 changes: 24 additions & 9 deletions Auth0/Credentials.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

private struct _StructCredentials {
private struct _A0Credentials {
let accessToken: String
let tokenType: String
let idToken: String
Expand Down Expand Up @@ -70,14 +70,14 @@ public final class Credentials: NSObject {
/// Custom description that redacts the tokens with `<REDACTED>`.
public override var description: String {
let redacted = "<REDACTED>"
let values = _StructCredentials(accessToken: redacted,
tokenType: self.tokenType,
idToken: redacted,
refreshToken: (self.refreshToken != nil) ? redacted : nil,
expiresIn: self.expiresIn,
scope: self.scope,
recoveryCode: (self.recoveryCode != nil) ? redacted : nil)
return String(describing: values).replacingOccurrences(of: "_StructCredentials", with: "Credentials")
let values = _A0Credentials(accessToken: redacted,
tokenType: self.tokenType,
idToken: redacted,
refreshToken: (self.refreshToken != nil) ? redacted : nil,
expiresIn: self.expiresIn,
scope: self.scope,
recoveryCode: (self.recoveryCode != nil) ? redacted : nil)
return String(describing: values).replacingOccurrences(of: "_A0Credentials", with: "Credentials")
}

// MARK: - Initializer
Expand Down Expand Up @@ -183,3 +183,18 @@ extension Credentials: NSSecureCoding {
public static var supportsSecureCoding: Bool { return true }

}

// MARK: - Internal Initializer

extension Credentials {

convenience init(from credentials: Credentials, idToken: String? = nil, refreshToken: String? = nil) {
self.init(accessToken: credentials.accessToken,
tokenType: credentials.tokenType,
idToken: idToken ?? credentials.idToken,
refreshToken: refreshToken ?? credentials.refreshToken,
expiresIn: credentials.expiresIn,
scope: credentials.scope)
}

}
Loading
Loading