iOS Civic App Connect
Civic App Connect is a library that allows a third party to connect with the Civic Secure Identity iOS app.
Introduction
This library allows you to connect to the Civic Secure Identity iOS application using the integration portal API. In order to use this library you will need to register yourself on the Integration Portal. Once registered you'll be able to provide the necessary information to the library to be able to connect your own iOS application to the Civic Secure Identity iOS application. The library also provides a Civic styled button that can be customized for your needs, or you could rather initiate the flow in any way you see fit for your application.
Requirements
- iOS 8.0+
- Xcode 10+
- Swift 4+
Installation
Cocoapods
Civic App Connect is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'CivicConnect'Carthage
To integrate Civic App Connect into your Xcode project using Carthage, specify it in your Cartfile:
github "civicteam/civic-connect-ios-public"
Run carthage update to build the framework and drag the built CivicConnect.framework into your Xcode project.
Getting Started
Sample App
To run the example project, clone the repo, and run pod install. Open up CivicConnect.xcworkspace with Xcode, in the Info.plist you will need to provide your CivicApplicationIdentifier and CivicSecret and run the CivicConnect-Example scheme. The sample project contains documented code to illustrate how to use the library.
Usage
This library is supported for both Objective-C and Swift and therefore each code snippet will contain its corresponding Objective-C and Swift section.
Initialization
Before being able to use the library, you will need to initialize it with the correct fields These values can be found on the Integration Portal when you have registered your application. Initializing the library requires the following fields:
| Name | Required | Description |
|---|---|---|
| Application Identifier | Yes | Identifier used to identify the third party. |
| Mobile Application Identifier | Yes | Identifier used by the mobile app. |
| Secret | No | Secret provided to the partner via the Integration Portal. Optional if you just require the JWT token. |
| Redirect Scheme | No | Scheme used to open the third party app. (This requires the third party to add a URL Types scheme in the Info.plist file. |
Swift
let connect = Connect(applicationIdentifier: <INSERT APPLICATION IDENTIFIER HERE>,
mobileApplicationIdentifier: <INSERT MOBILE APPLICATION IDENTIFIER HERE>,
secret: <INSERT SECRET HERE>,
redirectScheme: <INSERT REDIRECT SCHEME HERE>)Objective-C
CCConnect *connect = [[CCConnect alloc] initWithApplicationIdentifier:<INSERT APPLICATION IDENTIFIER HERE>
mobileApplicationIdentifier:<INSERT MOBILE APPLICATION IDENTIFIER HERE>
secret:<INSERT SECRET HERE>
redirectScheme:<INSERT REDIRECT SCHEME HERE>];A convenient way to initialize the library is to load the fields via the Info.plist:
Swift
let connect = try Connect.initialize(withBundle: Bundle.main, secret: <INSERT SECRET HERE>)Objective-C
NSError *error;
NSBundle *bundle = [NSBundle mainBundle];
CCConnect *connect = [CCConnect initializeWithBundle:bundle secret:<INSERT SECRET HERE> error:&error];The library will look through the Info.plist to find the following fields:
| Name | Type | Required |
|---|---|---|
| CFBundleIdentifier (Bundle Identifier - By default this should already exist) | String |
Yes |
| CivicApplicationIdentifier | String |
Yes |
| CivicSecret *deprecated | String |
Yes |
| CivicRedirectScheme | String |
No |
Please note: CivicRedirectScheme requires you to add a URL Type to the Info.plist with an identifier and at least one scheme. The scheme needs to be equal to the CivicRedirectScheme field.
Loading the fields from the Info.plist can throw the following errors if it cannot find the particular field:
| Error | Status Code | Message |
|---|---|---|
| cannotFindApplicationId | 901 | Cannot find application ID. Make sure you have 'CivicApplicationIdentifier' somewhere in your Info.plist. |
| cannotFindBundleId | 902 | Cannot find bundle ID. Make sure you have 'CFBundleIdentifier' somewhere in your Info.plist. |
| cannotFindSecret | 903 | Cannot find secret. Make sure you have 'CivicSecret' somewhere in your Info.plist. |
| redirectSchemeMismatch | 904 | Cannot find a matching URL scheme for 'CivicRedirectScheme'. Please ensure 'CivicRedirectScheme' matches one of the 'CFBundleURLTypes' 'CFBundleURLSchemes'. |
Connecting
Once you have an initialized instance of the Connect class, you'll be able to start the connection between your application and Civic. To initiate a flow, you can use the Connect.connect(withType:delegate:) method:
Swift
connect.connect(withType: .basic, delegate: self)Objective-C
[connect connectWithType:CCScopeRequestTypeBasic delegate:self];To determine what type to use, you'll need to know what information you require. The following table shows what information the types provide:
| Scope Request Type | Description |
|---|---|
ScopeRequestType.basicSignup |
Includes basic information such as email and phone number. |
ScopeRequestType.anonymousLogin |
Only includes the user ID. |
ScopeRequestType.proofOfResidence |
Includes the basic information, identity document and residential documents of the user. |
ScopeRequestType.proofOfIdentity |
Includes basic information such as email and phone number as well as information on an identity document. |
ScopeRequestType.proofOfAge |
Includes the age of the user. |
The delegate is the way the library communicates back to the partner via the following methods:
| Method | Description |
|---|---|
func connectDidFailWithError(_ error: ConnectError) |
This method is fired off when an error occurs inside the ConnectSession due to service errors, session errors, etc. |
func connectDidFinishWithUserId(_ userId: String, andUserInfo userInfo: [UserInfo]) |
This method is fired off when the ConnectSession has retrieved the user data from the servers. |
func connectDidChangeStatus(_ newStatus: ConnectStatus) |
This method is fired off when the state of the ConnectSession changes. It provides an easy way to know what is happening in the background. |
func connectShouldFetchUserData(withToken token: String) -> Bool |
This method is fired off when the ConnectSession receives the JWT token from the server. At this point we ask the delegate whether we should continue to fetch the user data using the JWT token. Returning true will allow the ConnectSession to retrieve the user data, while false ends the session. For Swift this method is implmemented by default and returns true through the use of extensions. |
After connecting with Civic, a ConnectSession is created to handle the connection between the partner app and Civic. Once the ConnectSession is created, you have two ways of handling the session. Either handling the URL that was used to open the partner app via the Connect.handle(url:) method or by calling the Connect.startPollingForUserData() method. These methods will be described below.
Handling URL
Handling a URL will allow the library to determine whether the Civic application opened up/redirected to the partner application and start retrieving the user data. In order for this feature to work, you must setup a URL Type with a URL Scheme in the Info.plist. (See the example project to see how this is done)
A convenient place to handle the URL is in the AppDelegate on the func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool method as follows:
Swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return connect.handle(url: url)
}Objective-C
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation {
return [connect handleUrl:url];
}Internally this will check if the URL was initiated from the Civic application. You can manually check the URL via Connect.canHandle(url:). If the session can handle the URL then it will call Connect.startPollingForUserData() internally.
Start Polling For User Data
If you do not choose to handle the URL method or you would prefer not to use the redirect mechanism, you can rather choose to start polling for the user data manually. When calling this method, it will initiate a timer that contantly checks when the Civic application has finished the flow.
Swift
connect.startPollingForUserData()Objective-C
[connect startPollingForUserData];Stop Polling For User Data
At anytime the session is started, you can call Connect.stopPollingForUserData() to invalidate the timer.
Swift
connect.stopPollingForUserData()Objective-C
[connect stopPollingForUserData];Reset
You can reset the status of the of Connect instance by calling the Connect.reset() function.
Swift
connect.reset()Objective-C
[connect reset];Connect Button
If you would like a Civic styled button, you can use the ConnectButton class which is just a subclass of UIButton. In order to create one, you will need to provide a Connect instance, a ConnectDelegate and optionally the ScopeRequestType:
Swift
let connectButton = ConnectButton(<INSERT CONNECT HERE>,
type: <INSERT SCOPE REQUEST TYPE HERE (DEFAULTS TO .basic)>,
delegate: <INSERT CONNECT DELEGATE HERE>)Objective-C
CCConnectButton *connectButton = [[CCConnectButton alloc] initWithConnect:<INSERT CONNECT HERE>
type:<INSERT SCOPE REQUEST TYPE HERE>
delegate:<INSERT CONNECT DELEGATE HERE>];Once created, you can customize the title and image of the button:
Swift
connectButton.setConnectTitle(<INSERT TITLE HERE>, image: <INSERT IMAGE HERE>)
connectButton.setConnectTitle(<INSERT TITLE HERE>)Objective-C
[connectButton setConnectTitle:<INSERT TITLE HERE> image:<INSERT IMAGE HERE>];
[connectButton setConnectTitle:<INSERT TITLE HERE>];The scope request type can also be changed at anytime:
Swift
connectButton.setType(<INSERT SCOPE REQUEST TYPE HERE>)Objective-C
[connectButton setType:<INSERT SCOPE REQUEST TYPE HERE>];Anytime the ConnectButton is tapped, it will start connecting with the Civic application and display a loading indicator on the button. The loading indicator will disappear once the session has received either an error or success response.
Errors
The following table shows the potential errors that can occur via the library:
| Error | Status Code | Message |
|---|---|---|
| cannotFindApplicationId | 901 | Cannot find application ID. Make sure you have 'CivicApplicationIdentifier' somewhere in your Info.plist. |
| cannotFindBundleId | 902 | Cannot find bundle ID. Make sure you have 'CFBundleIdentifier' somewhere in your Info.plist. |
| cannotFindSecret | 903 | Cannot find secret. Make sure you have 'CivicSecret' somewhere in your Info.plist. |
| redirectSchemeMismatch | 904 | Cannot find a matching URL scheme for 'CivicRedirectScheme'. Please ensure 'CivicRedirectScheme' matches one of the 'CFBundleURLTypes' 'CFBundleURLSchemes'. |
| cannotParseResponse | 911 | Cannot parse response from server. |
| cannotParseResponseData | 912 | Cannot parse response data from server. |
| invalidUrl | 913 | Invalid url. |
| invalidRequest | 914 | Invalid request. |
| invalidSession | 921 | Invalid session. |
| mobileUpgrade | 922 | Mobile upgrade required. |
| userCancelled | 923 | User cancelled scope request. |
| verifyError | 924 | Error occurred during verification. |
| userDataNotAvailable | 202 | User data is still not available. Try poll again later. |
| scopeRequestTimeOut | 925 | Scope request timed out. |
| verificationFailed | 931 | Failed to verify the response. |
| decryptionFailed | 932 | Failed to decrypt response data. |
| secretNotFound | 933 | Cannot find secret. Please ensure you provide the library with a valid secret. |
| decodingFailed | 997 | Failed to decode the json to an object. |
| authenticationUnknownError | 998 | Unknown authentication error. |
| unknown | 999 | Unknown error. |
The following table shows the potential errors that can occur via the server api:
| Error | Status Code | Message |
|---|---|---|
| badRequest | 400 | Check the response ‘message’ field for details. |
| unauthorized | 401 | Authentication failed. |
| methodNotAllowed | 405 | You tried to access an invalid method. |
| insufficientFunds | 409 | Unauthorized: Insufficient funds. |
| tooManyRequests | 429 | Your request was throttled by our gateway. |
| internalServer | 500 | We had a problem with our server. Try again later. |
| timeOut | 504 | Endpoint Request Timed-out Exception. |
FAQ
What do we use for the redirect scheme?
You can use any unique scheme that is valid for your app.
How do we get the UIImage from the UserInfo?
Some UserInfo objects have a value encoded in base 64, an example would be for the documents.genericId.image UserInfo object, we would decode to an UIImage with the following code snippet:
func decodeBase64ToImage(_ userInfo: UserInfo) -> UIImage? {
guard let data = Data(base64Encoded: userInfo.value, options: .ignoreUnknownCharacters) else {
return nil
}
return UIImage(data: data)
}What does it mean when I get Unauthorized: no platforms found for provided app_id?
It means you have supplied the incorrect Application Identifier. Please ensure you have the correct App ID from the Integration Portal under the configured application.
What does it mean when I get Unauthorized: mobileId not found in partner platform list?
It means you have not supplied the correct Bundle ID or have not configured your mobile application on the Integration Portal.
How do I just retrieve the JWT token so that I can handle the user data on my side?
In order to retrieve the JWT token only, in your implementation for the ConnectDelegate, implement the func connectShouldFetchUserData(withToken token: String) -> Bool function and return false. The token provided in that function is the JWT token and can be handled by whichever way needed.
Author
See also the list of contributors who participated in this project.
Contribution
Find contributing guidelines in the CONTRIBUTING file.
License
Civic App Connect is available under the MIT license. See the LICENSE file for more info.