How to implement Vision API with 'Service account' in the iOS app? - ios

I'm trying to implement Google Vision API for the my app via REST.
https://cloud.google.com/vision/docs/pdf
Is there any examples or any suggestions how to do this?
Documentation says that they require service account token but can't find any examples how to get service account token from iOS app side.
I've tried via GTMAppAuth but getting 403 error
I was able to generate this token from my mac machine and all worked, but token has limited life time and after 3-4 hours it expiries

I figured out and did this by my own.
Documentation:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth
1)You need to create service account, download p12 or JSON file. JSON file contains private key and public certificate url. You need to create text file, where private key on the top, certificate on the bottom and run this command:
openssl pkcs12 -export -in file_name.txt -out file_name.p12
2)Create jwt token with parameters which described in the documentation (for jwt I used this library: https://github.com/yourkarma/JWT)
3) Make POST request to https://www.googleapis.com/oauth2/v4/token with parameters which described in the documentation
Hope it will help somebody in the future

First you need to get a Bearer Token... You can get it by following the instructions on this page...
https://cloud.google.com/vision/docs/auth#using_an_api_key
The Bearer Token will not expire, you can implemented it to your code...
A Basic Version on how to add a Bearer Token to your Request in Swift is Shown below... All other Stuff goes as a JSON in Data Format to the "body" parameter
This Link will show you how to build up the JSON for your request to Vision... https://cloud.google.com/vision/docs/using-curl
class APIHandler {
private let API_TOKEN = "Your Token"
func requestVisionFromREST(body: Data, completion: #escaping (_ response: Data?)-> Void) {
let config = URLSessionConfiguration.default
var headers = [String:String]()
headers["Authorization"] = "Bearer \(API_TOKEN)"
config.httpAdditionalHeaders = headers
var urlRequest = URLRequest(url: URL(string: "YOUR URL")!)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = body
URLSession(configuration: config).dataTask(with: urlRequest) { (data, response, error) in
completion(data)
}.resume()
}
}
EDIT
If you want to use the Google Sign In instead of the OAuth Method, to reauth your apps and get a fresh token, you can follow the instructions on the googles instructions page below:
https://developers.google.com/identity/sign-in/ios/offline-access

Related

How to handle '€' in a password in a HTTP post request

I have an iOS app which sends a HTTP request for the login to our Webserver. The login basically works fine, but as soon as someone got a '€' in his password the login fails.
This bug only happens in the app. We also have a web application, which sends the same login request to the same webserver and I can perfectly log in when I do that in my browser, even if there is a '€' in my password.
Here's the function that generates the request:
func SignOn() {
var request = Helper.getURLRequest(str: str, method: "POST")
guard let httpBody = try? JSONEncoder().encode(Helper.Logon.init(domain: String(userDomain[0]), user: String(userDomain[1]), p: ""))else { return }
request.httpBody = httpBody
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
urlSession.dataTask(with: request) { (data, response, error) in
do {
guard let data = data else { throw Helper.MyError.NoConnection }
Helper.isAuthenticated = try JSONDecoder().decode(Helper.Authentication.self, from: data)
task.leave()
} catch {
[...]
}
static func getURLRequest(str: String, method: String) -> URLRequest {
let url = URL(string: str)
var request = URLRequest(url: url!)
let loginString = "\(Helper.loggedOnUserWithDomain):\(Helper.loggedOnUserPassword)"
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData!.base64EncodedString()
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = method
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
return request
}
SignOn() gets called as soon as the user presses the "login" button in the app. Username and password are stored in two variables in my Helper class.
SignOn() will then call a function that generates the request - also in my Helper class.
I double checked every step in getURLRequest(). loginString and loginData both keep the € and they are perfectly displaying the character when I let Xcode print the variables.
I then checked the base64 string. Let's say someone enters "t€stpassword". The encoded base64 string should be VOKCrHN0cGFzc3dvcmQ=, which the function got right. I then let the function decode the base64 string again and checked if "t€stpassword" was the result, which again was true.
Then I checked the request with HTTP interception, but it also had the '€' in his body.
I already tried to percent escape the '€' character but that does also not work. The '€' gets percent escaped correctly, but I think the web server can't handle it then, I don't really know tbh. I used this method: how to http post special chars in swift
I'm out of ideas what I'm doing wrong here. I'm pretty new to Swift so I don't want to rule out, that I'm missing something obvious. Could the web server be the issue? But as I said, the login is working when doing it in a browser, so the server cannot be the issue, right?
According "The 'Basic' HTTP Authentication Scheme" in RFC 7617, section 3:
3. Internationalization Consideration
User-ids or passwords containing characters outside the US-ASCII
character repertoire will cause interoperability issues, unless both
communication partners agree on what character encoding scheme is to
be used. Servers can use the new 'charset' parameter (Section 2.1)
to indicate a preference of "UTF-8", increasing the probability that
clients will switch to that encoding.
Furthermore,
For the user-id, recipients MUST support all characters defined in
the "UsernameCasePreserved" profile defined in Section 3.3 of
RFC7613, with the exception of the colon (":") character.
For the password, recipients MUST support all characters defined in
the "OpaqueString" profile defined in Section 4.2 of RFC7613.
The "recipient" here is the backend. The referenced RFCs in the cited paragraphs clearly describe how the backend should process the Unicode characters and how to perform the comparison operator. You might test the server against the specification to figure out whether the server behaves correctly.
The client however, should at least check for a semicolen in either the password or user-id which would be an invalid credential for Basic HTTP Authentication.
So, your code should work, unless the backend does not want to handle Unicode. If this is the case, only allow ASCII on the client side.
When the authentication fails, a server might message the expected charset in the response in the Authenticate header:
WWW-Authenticate: Basic realm="foo", charset="UTF-8"
However, specifying a charset parameter is "purely advisory". We can't rely on the server sending this.
Basic HTTP is what the name suggests: a basic authentication scheme. It has been deprecated for a while now.
If possible, use a more secure and a more resilient authentication scheme.

How to call REST API deployed in AWS on iOS app?

I am new to AWS and trying to implement a webservice API GET call on iOS app. So following is the postman I have and trying to implement the same in iOS app;
But I am confused in setting this header on URLSession requests. I am not seeing much documentations regarding this. Looking forward for some help in implementing this on iOS app.
Tried to implement the Swift code generated in Postman :
But this implementation returns a Forbidden error message , so i believe some different implementation is needed to connect AWS
Can you show us what you have tried with URLSession so far ?
As said by #burnsi, you show look at the raw headers that are working for you in Postman and try to replicate those.
Some headers should always be used for all requests (given a particular URLSession), so you should consider configuring your session using httpAdditionalHeaders:
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = [
"Accept-Encoding": "application/json",
"Content-Type": "application/json"
]
let session: URLSession = URLSession(configuration: configuration)
For headers that are request-specific or likely to change over time (like an authorization token), you should add them to the request itself using setValue(_:forHTTPHeaderField:):
var request: URLRequest = URLRequest(url: url)
request.setValue("XYZ", forHTTPHeaderField: "Authorization")
Then you should perform the request using:
session.dataTask(with: request, completionHandler: { (data, response, error) in
print("Data: \(data?.debugDescription)\nResponse: \(response?.debugDescription)\nError: \(error?.debugDescription)")
})
Let us know what this code prints out for you and I'll try to help more!

Alamofire request fails with nil response

I am getting numerous failed requests with Alamofire 5.3, where the response object itself is nil, or the error is "cannot parse response". I can see from the server logs that all of those requests are returning valid.
Here is my setup:
API manager class:
let config = Alamofire.Session.default.session.configuration
self.session = Alamofire.Session(configuration: config, interceptor: AccessTokenInterceptor())
AccessTokenInterceptor:
class AccessTokenInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: #escaping (AdapterResult<URLRequest>) -> Void) {
var adaptedRequest = urlRequest
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Accept")
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let token = SettingsManager.shared.userToken {
adaptedRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
}
completion(.success(adaptedRequest))
}
}
This interceptor inserts my auth token from SettingsManager
I am also using the standard router for URLRequestConvertible where encoding is done by JSON serialization (dictionary) or Codable protocol (objects)
case .login(let body):
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
case .register(let object):
request.httpBody = try JSONEncoder().encode(object)
What is strange is that I don't think I'm doing anything different from the many other times I've used Alamofire and now the first request I make fails but the following one succeeds. If I remove the interceptor, there is no change.
If I inspect the outgoing headers or body content, it all seems normal, but the response from Alamofire is nil.
UPDATE: By using OS_ACTIVITY_MODE and iOS 13 I was able to see that it was complaining about the request headers and protocol. The server is on Elastic Beanstalk so I've been trying to mess with the SSL policy but still the first request fails every time.
This turned into quite the rabbit hole, so in the interest of community improvement, here is what I found.
After searching through the activity log errors, I noticed that iOS was complaining about an invalid header type -- upgrade. Searching for that value I found this question about removing the header. I learned that Apache acts as a proxy on Elastic Beanstalk but there is a mix up for HTTP/2 headers in the request, and iOS does not like that.
To get away from the header value, I ended up switching to Nginx proxy. Since my application uses Laravel, I then needed to deal with correcting the pretty URLs. To do that I found this answer. Now my web and mobile application both seem to be getting along nicely.

Does Alamofire store the cookies automatically?

I'm new to Alamofire so I'm sorry if this it's a noob question: this framework stores the cookies automatically?
This is because I have a simple request like this:
Alamofire.request(.POST, loginURL, parameters: ["fb_id": fbId, "fb_access_token": fbToken])
.responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
if let JSON = response.result.value {
print("loginURL - JSON: \(JSON)")
}
}
this request response with a cookie session that I need to do other requests for security reason; the strange thing is that like magic I already can do the other requests after this first POST without read manually the cookie and store it. I'm sure the other requests need the cookie session because they fail on postman for example but not here.
It's just a feature? Because I can't find anything on that also on the official GitHub page.
Yes! Alamofire is basically a wrapper around NSURLSession. Its manager uses a default NSURLSessionConfiguration by calling defaultSessionConfiguration().
As its github page says under Advanced Usage section:
Alamofire is built on NSURLSession and the Foundation URL Loading System. To make the most of this framework, it is recommended that you be familiar with the concepts and capabilities of the underlying networking stack.
And under Manager section:
Top-level convenience methods like Alamofire.request use a shared instance of Alamofire.Manager, which is configured with the default NSURLSessionConfiguration.
And the NSURLSessionConfiguration reference for defaultSessionConfiguration() says:
The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain. It also stores cookies (by default) in the same shared cookie store as the NSURLConnection and NSURLDownload classes.
For those who use Moya and want to disable stored cookies
(fixing the X-CSRF-Token request header is missing)
Very basic example:
public final class DisableCookiePlugin: PluginType {
public init() {
}
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var mutableRequest = request
mutableRequest.httpShouldHandleCookies = false
return mutableRequest
}
}
And then use it
MoyaProvider<Api>(
plugins: [
//NetworkLoggerPlugin(configuration: .init(logOptions: .verbose)),
DisableCookiePlugin()
]

How to create signature for twitter in Salesforce and what are the steps for signature creation

I need a signature for my first request to twitter :
My code create a Post request for this end Url https://api.twitter.com/oauth/request_token and I have to set a header which is mention below in which I need signature.
I have only call back Url, consumer_key. I have created nonce and timestamp. I need signature.
Authorization Header:
OAuth oauth_nonce="XXXXXXXXX", oauth_callback="XXXX", oauth_signature_method="HMAC-SHA1", oauth_timestamp="XXXXXX", oauth_consumer_key="XXXXXX", oauth_signature="HOW I CREATE THIS", oauth_version="1.0"
You can use oauth module https://github.com/ciaranj/node-oauth
var oauth=require('oauth');
var consumer = new oauth.OAuth(
"https://twitter.com/oauth/request_token", "https://twitter.com/oauth/access_token",
_twitterConsumerKey, _twitterConsumerSecret, "1.0A", "http://127.0.0.1:8080/sessions/callback", "HMAC-SHA1");
then generating signature like this :
var parameters = consumer._prepareParameters("user_access_token", "user_access_token_secret", "http_method", "request_url");
var headers = consumer._buildAuthorizationHeaders(parameters);
parameters array contains signature, also you can build authorization headers if needed. Hope it helps :)
I think twitter has a nicely written document on creating a signature.
https://dev.twitter.com/docs/auth/creating-signature
Check it out...
Edit: I noticed your oauth_* parameters are not in order.
Don't forget to get them in order.

Resources