-
-
Notifications
You must be signed in to change notification settings - Fork 6
Description
New Feature / Enhancement Checklist
- I am not disclosing a vulnerability.
- I have searched through existing issues.
Current Limitation
Parse-Swift currently supports Anonymous authentication (ParseAnonymous) and Apple authentication (see all adapters below).
Microsoft Graph, Meetup, or any other Parse Server supported authentication method works, but they currently don't have helper methods to connect to them as easily as using ParseApple
.
Populating authData
directly requires knowing the correct key/values for the respective adapter and can quickly lead to mistakes in code during login, linking, etc.
Feature / Enhancement Description
To make the process simpler, developers can easily add support for additional 3rd party authentication methods by using ParseGoogle as a template.
Example Use Case
We are encouraging developers to open PR's (see the contributors guide) to add missing authentication methods by following the process below:
- Create a new folder inside of the
Sources->ParseSwift->Authentication->3rd Party
folder and name itParseNAME
, for exampleParseFacebook
- Inside of the new folder, create a new files named:
ParseNAME.swift
,ParseNAME-async.swift
,ParseNAME-combine.swift
. - Copy the code from ParseGoogle.swift, ParseGoogle+async.swift, ParseGoogle+combine.swift into the new respective adapter files you are creating.
- Refactor by: a) Find/replace
Google
->Name
, b) Find/replacegoogle
->name
- Modify/tailor the helper methods to work with the specific SDK. This will require modifying AuthenticationKeys in
ParseNAME.swift
. - Add any additional methods that may be needed to verify inputs or simplify passing in values to the method.
- Update documentation/comments in the code to reflect the new adapter
- Copy/modify the ParseGoogleTests.swift, ParseGoogleCombineTests.swift files to add the respective test cases.
- Refactor test files by: a) Find/replace
Google
->Name
, b) Find/replacegoogle
->name
- Submit your pull request for review
Note that the dependency of the the respective framework, i.e. import FBSDKCoreKit
should not be part of the PR as Parse-Swift is dependency free. Instead, the implementation should just ensure the necessary AuthenticationKeys
are captured to properly authenticate the respective framework with a Parse Server. The developer using the respective authentication method is responsible for adding any dependencies to their app. This lets the developer manage and use any version of any authentication SDK's they want, they simply need to specify the required keys that the Parse Server requires.
If you need help, feel free to ask questions here or in your added PR.
Potential adapters (checked one’s are already implemented in Parse-Swift):
- Apple
- Github
- Janrain Capture
- Janrain Engage
- Keycloak
- LDAP
- Meetup
- Microsoft Graph
- OTP
- PhantAuth
- Spotify
- vKontakte
Alternatives / Workarounds
To authenticate without helper methods, you can use the following available on any ParseUser
:
Parse-Swift/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication.swift
Lines 231 to 382 in 11bb8e6
/** | |
Makes an *asynchronous* request to log in a user with specified credentials. | |
Returns an instance of the successfully logged in `ParseUser`. | |
This also caches the user locally so that calls to *current* will use the latest logged in user. | |
- parameter type: The authentication type. | |
- parameter authData: The data that represents the authentication. | |
See [supported 3rd party authentications](https://docs.parseplatform.org/parse-server/guide/#supported-3rd-party-authentications) for more information. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- parameter callbackQueue: The queue to return to after completion. Default value of .main. | |
- parameter completion: The block to execute. | |
It should have the following argument signature: `(Result<Self, ParseError>)`. | |
*/ | |
static func login(_ type: String, | |
authData: [String: String], | |
options: API.Options, | |
callbackQueue: DispatchQueue = .main, | |
completion: @escaping (Result<Self, ParseError>) -> Void) { | |
Task { | |
do { | |
_ = try await Self.current() | |
Self.link(type, | |
authData: authData, | |
options: options, | |
callbackQueue: callbackQueue, | |
completion: completion) | |
} catch { | |
let body = SignupLoginBody(authData: [type: authData]) | |
do { | |
try await signupCommand(body: body) | |
.execute(options: options, | |
callbackQueue: callbackQueue, | |
completion: completion) | |
} catch { | |
let parseError = error as? ParseError ?? ParseError(swift: error) | |
callbackQueue.async { | |
completion(.failure(parseError)) | |
} | |
} | |
} | |
} | |
} | |
// MARK: 3rd Party Authentication - Link | |
/** | |
Whether the `ParseUser` is logged in with the respective authentication string type. | |
- parameter type: The authentication type to check. The user must be logged in on this device. | |
- returns: **true** if the `ParseUser` is logged in via the repective | |
authentication type. **false** if the user is not. | |
*/ | |
func isLinked(with type: String) -> Bool { | |
guard let authData = self.authData?[type] else { | |
return false | |
} | |
return authData != nil | |
} | |
/** | |
Strips the *current* user of a respective authentication type. | |
- parameter type: The authentication type to strip. | |
- returns: The user whose autentication type was stripped. | |
*/ | |
func strip(_ type: String) -> Self { | |
var user = self | |
user.authData?.updateValue(nil, forKey: type) | |
return user | |
} | |
/** | |
Unlink the authentication type *asynchronously*. | |
- parameter type: The type to unlink. The user must be logged in on this device. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- parameter callbackQueue: The queue to return to after completion. Default value of .main. | |
- parameter completion: The block to execute. | |
It should have the following argument signature: `(Result<Self, ParseError>)`. | |
- note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer | |
desires a different policy, it should be inserted in `options`. | |
*/ | |
func unlink(_ type: String, | |
options: API.Options = [], | |
callbackQueue: DispatchQueue = .main, | |
completion: @escaping (Result<Self, ParseError>) -> Void) { | |
var options = options | |
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) | |
let immutableOptions = options | |
if self.isLinked(with: type) { | |
guard let authData = self.strip(type).authData else { | |
let error = ParseError(code: .otherCause, | |
message: "Missing \"authData\".") | |
callbackQueue.async { | |
completion(.failure(error)) | |
} | |
return | |
} | |
let body = SignupLoginBody(authData: authData) | |
Task { | |
do { | |
try await self.linkCommand(body: body) | |
.execute(options: immutableOptions, | |
callbackQueue: callbackQueue, | |
completion: completion) | |
} catch { | |
let parseError = error as? ParseError ?? ParseError(swift: error) | |
callbackQueue.async { | |
completion(.failure(parseError)) | |
} | |
} | |
} | |
} else { | |
callbackQueue.async { | |
completion(.success(self)) | |
} | |
} | |
} | |
/** | |
Makes an *asynchronous* request to link a user with specified credentials. The user should already be logged in. | |
Returns an instance of the successfully linked `ParseUser`. | |
This also caches the user locally so that calls to *current* will use the latest logged in user. | |
- parameter type: The authentication type. | |
- parameter authData: The data that represents the authentication. | |
See [supported 3rd party authentications](https://docs.parseplatform.org/parse-server/guide/#supported-3rd-party-authentications) for more information. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- parameter callbackQueue: The queue to return to after completion. Default value of .main. | |
- parameter completion: The block to execute. | |
It should have the following argument signature: `(Result<Self, ParseError>)`. | |
- note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer | |
desires a different policy, it should be inserted in `options`. | |
*/ | |
static func link(_ type: String, | |
authData: [String: String], | |
options: API.Options = [], | |
callbackQueue: DispatchQueue = .main, | |
completion: @escaping (Result<Self, ParseError>) -> Void) { | |
Task { | |
var options = options | |
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData)) | |
let body = SignupLoginBody(authData: [type: authData]) | |
do { | |
try await Self.current().linkCommand(body: body) | |
.execute(options: options, | |
callbackQueue: callbackQueue, | |
completion: completion) | |
} catch { | |
let parseError = error as? ParseError ?? ParseError(swift: error) | |
callbackQueue.async { | |
completion(.failure(parseError)) | |
} | |
} | |
} | |
} |
and here:
Parse-Swift/Sources/ParseSwift/Authentication/Protocols/ParseAuthentication+combine.swift
Lines 34 to 89 in 11bb8e6
/** | |
Makes an *asynchronous* request to log in a user with specified credentials. | |
Publishes an instance of the successfully logged in `ParseUser`. | |
This also caches the user locally so that calls to *current* will use the latest logged in user. | |
- parameter type: The authentication type. | |
- parameter authData: The data that represents the authentication. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- returns: A publisher that eventually produces a single value and then finishes or fails. | |
*/ | |
static func loginPublisher(_ type: String, | |
authData: [String: String], | |
options: API.Options = []) -> Future<Self, ParseError> { | |
Future { promise in | |
Self.login(type, | |
authData: authData, | |
options: options, | |
completion: promise) | |
} | |
} | |
/** | |
Unlink the authentication type *asynchronously*. Publishes when complete. | |
- parameter type: The type to unlink. The user must be logged in on this device. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- returns: A publisher that eventually produces a single value and then finishes or fails. | |
*/ | |
func unlinkPublisher(_ type: String, | |
options: API.Options = []) -> Future<Self, ParseError> { | |
Future { promise in | |
self.unlink(type, | |
options: options, | |
completion: promise) | |
} | |
} | |
/** | |
Makes an *asynchronous* request to link a user with specified credentials. The user should already be logged in. | |
Publishes an instance of the successfully linked `ParseUser`. | |
This also caches the user locally so that calls to *current* will use the latest logged in user. | |
- parameter type: The authentication type. | |
- parameter authData: The data that represents the authentication. | |
- parameter options: A set of header options sent to the server. Defaults to an empty set. | |
- returns: A publisher that eventually produces a single value and then finishes or fails. | |
*/ | |
static func linkPublisher(_ type: String, | |
authData: [String: String], | |
options: API.Options = []) -> Future<Self, ParseError> { | |
Future { promise in | |
Self.link(type, | |
authData: authData, | |
options: options, | |
completion: promise) | |
} | |
} |