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

Adding Support for more 3rd Party Authentication SDK's #55

Open
12 of 22 tasks
cbaker6 opened this issue Jan 21, 2021 · 2 comments
Open
12 of 22 tasks

Adding Support for more 3rd Party Authentication SDK's #55

cbaker6 opened this issue Jan 21, 2021 · 2 comments
Labels
bounty:$50 Bounty applies for fixing this issue (Parse Bounty Program) type:feature New feature or improvement of existing feature

Comments

@cbaker6
Copy link
Contributor

cbaker6 commented Jan 21, 2021

New Feature / Enhancement Checklist

Current Limitation

Parse-Swift currently supports Anonymous authentication (ParseAnonymous) and Apple authentication (see all adapters below).

Microsoft Graph, Instagram, and any Parse Server supported authentication method do work, 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:

  1. Create a new file inside the 3rd Party folder and name it ParseNAME.swift, for example ParseFacebook .
  2. Copy the code from ParseGoogle.swift, ParseGoogle+async.swift, ParseGoogle+combine.swift into the new respective adapter files you are creating.
  3. Refactor by: a) Find/replace Google -> Name, b) Find/replace google -> name
  4. Modify/tailor the helper methods to work with the specific SDK. This will require modifying AuthenticationKeys in the file.
  5. Add any additional methods that may be needed to verify inputs or simplify passing in values to the method.
  6. Update documentation/comments in the code to reflect the new adapter
  7. Copy/modify the ParseGoogleTests.swift, ParseGoogleCombineTests.swift file to add the respective test cases.
  8. Refactor test files by: a) Find/replace Google -> Name, b) Find/replace google -> name
  9. Submit your pull request for review

Note that the dependency of the the respective framework, i.e. import FBSDKCoreKit shouldn't 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 for 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
  • Facebook
  • Github
  • Google
  • Instagram
  • Janrain Capture
  • Janrain Engage
  • Keycloak
  • LDAP
  • LinkedIn
  • Meetup
  • Microsoft Graph
  • PhantAuth
  • QQ
  • Spotify
  • Twitter
  • vKontakte
  • WeChat
  • Weibo

Alternatives / Workarounds

To authenticate without helper methods, you can use the following available on any ParseUser:

// MARK: 3rd Party Authentication - Login
/**
Makes a *synchronous* request to login 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.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- throws: An error of type `ParseError`.
- returns: An instance of the logged in `ParseUser`.
If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`.
*/
static func login(_ type: String,
authData: [String: String],
options: API.Options) throws -> Self {
let body = SignupLoginBody(authData: [type: authData])
return try signupCommand(body: body).execute(options: options)
}
/**
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.
- 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,
completion: @escaping (Result<Self, ParseError>) -> Void) {
let body = SignupLoginBody(authData: [type: authData])
signupCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
}
// MARK: 3rd Party Authentication - Link
func isLinked(with type: String) -> Bool {
guard let authData = self.authData?[type] else {
return false
}
return authData != nil
}
func unlink(_ type: String,
options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Self, ParseError>) -> Void) {
guard let current = Self.current,
current.authData != nil else {
let error = ParseError(code: .unknownError, message: "Must be logged in to unlink user")
callbackQueue.async {
completion(.failure(error))
}
return
}
if current.isLinked(with: type) {
guard let authData = current.apple.strip(current).authData else {
let error = ParseError(code: .unknownError, message: "Missing authData.")
callbackQueue.async {
completion(.failure(error))
}
return
}
let body = SignupLoginBody(authData: authData)
current.linkCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
} else {
callbackQueue.async {
completion(.success(self))
}
}
}
/**
Makes a *synchronous* 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.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- throws: An error of type `ParseError`.
- returns: An instance of the logged in `ParseUser`.
If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`.
*/
static func link(_ type: String,
authData: [String: String],
options: API.Options) throws -> Self {
guard let current = Self.current else {
throw ParseError(code: .unknownError, message: "Must be logged in to link user")
}
let body = SignupLoginBody(authData: [type: authData])
return try current.linkCommand(body: body).execute(options: options)
}
/**
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.
- 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 link(_ type: String,
authData: [String: String],
options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Self, ParseError>) -> Void) {
guard let current = Self.current else {
let error = ParseError(code: .unknownError, message: "Must be logged in to link user")
callbackQueue.async {
completion(.failure(error))
}
return
}
let body = SignupLoginBody(authData: [type: authData])
current.linkCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
}

and here:

// MARK: 3rd Party Authentication - Login Combine
/**
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)
}
}

3rd Party References

@cbaker6
Copy link
Contributor Author

cbaker6 commented Feb 14, 2021

To see an example PR following this process, look through the files added in #79 which added ParseLDAP or #97 which added ParseFacebook and ParseTwitter.

@parse-github-assistant
Copy link

parse-github-assistant bot commented Jan 1, 2022

Thanks for opening this issue!

  • 🎉 We are excited about your ideas for improvement!

@mtrezza mtrezza unpinned this issue Nov 4, 2022
@mtrezza mtrezza added the bounty:$50 Bounty applies for fixing this issue (Parse Bounty Program) label Nov 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bounty:$50 Bounty applies for fixing this issue (Parse Bounty Program) type:feature New feature or improvement of existing feature
Projects
None yet
Development

No branches or pull requests

2 participants