I'm using custom authorisation with the Uber iOS SDK, and having trouble creating the AccessToken in my iOS code. This is the response I get from my server with what appears to be a valid access token:
{
"access_token":"token here",
"expires_in":2592000,
"token_type":"Bearer",
"scope":"all_trips request",
"refresh_token":"refresh token here",
"last_authenticated":0
}
I then pass this to the AccessToken initialiser, like so:
let jsonString = //response from server as above
let accessToken = AccessToken(tokenString: jsonString)
My access token is created (ie. non-nil), but none of the relevant property are populated.
accessToken //non-nil
accessToken.expirationDate //nil
accessToken.refreshToken //nil
Strangely, accessToken.tokenString contains the original jsonString from above.
Am I doing something wrong?
Edit
Digging through the AccessToken.swift source file of the Uber project, I find this:
#objc public init(tokenString: String) {
super.init()
self.tokenString = tokenString
}
It seems like it never actually creates the refreshToken etc.
The tokenString is meant to be just the access token itself, as you observed. If you want to parse the JSON itself, I would suggest using the fact that the model conforms to the Decodable protocol, and pass in your JSON through that method.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let accessToken = try? decoder.decode(AccessToken.self, from: jsonData)
// If you need to convert a string to data, use String.data(using: .utf8)!
Related
I want to know how can I force all the properties from this struct to be able to send a POST request to our API?
First of all. I need all those optional properties because I make a GET request, I receive all those documents, I process the data, I add the file property (which is an object) then I need to send all those documents back to our server with a file added.
We have our Document
struct Document: Codable {
let allowedFormats: [String]?
let processWhereApply: [String]?
let isRequired: Bool?
let id, key, title, description: String?
var file: File?
// More properties
}
But it fails every time, because I'm not sending a string for example. I'm sending an Optional<String>
Is there anyway possible I can "force" all those properties to send them back? without like writting 25 lines of guard let var = var else { return } ?
I'm sending the POST request with the parameters like this
let params = [
"userId": userId,
"groupId": groupId,
"fileDocuments": documents! //sends all properties optional
] as [String: Any]
Api().saveDocuments(params: params)
I'm assuming that you are sending the data back as Json. In that case just use the Json encode method to convert the struct to Json which can be sent as a POST request. Json encode will deal with the null value issue by setting a corresponding value to the key in your json if the value exists and not creating the key if it doesn't exist.
For example just to get the json:
let doc1 = Document(!here you will be initialising variables!)
// below gives you json as data
let json = try! JSONEncoder().encode(doc1)
// if you want your json as string
let str = String(decoding: json, as: UTF8.self)
Here is an example making an alamofire POST request. In the case of alamofire it automatically encodes your struct as long as it conforms to Codable:
let doc1 = Document(!here you will be initialising variables!)
AF.request("https://yoururl.com",method: .post,parameters: doc1, encoder: JSONParameterEncoder.default).responseJSON { response in
switch response.result {
case .success(let json):
print("good response")
break
case .failure(let error):
print("bad response"
}
}
How to translate my login user URLSession code into Siesta framework code? My current attempt isn't working.
I've looked at the example in the GithubBrowser but the API I have doesn't work like that.
The issue is that the user structure is kind of split by how the endpoint in the API I'm consuming works. The endpoint is http://server.com/api/key. Yes, it really is called key and not user or login. Its called that by the authors because you post a user/pass pair and get a key back. So it takes in (via post) a json struct like:
{"name": "bob", "password": "s3krit"}
and returns as a response:
{"token":"AEWasBDasd...AAsdga"}
I have a SessionUser struct:
struct SessionUser: Codable
{
let name: String
let password: String
let token: String
}
...which encapsulates the state (the "S" in REST) for the user. The trouble is name & password get posted and token is the response.
When this state changes I do my:
service.invalidateConfiguration() // So requests get config change
service.wipeResources() // Scrub all unauthenticated data
An instance is stored in a singleton, which is picked up by the configure block so that the token from the API is put in the header for all other API requests:
configure("**") {
// This block ^ is run AGAIN when the configuration is invalidated
// even if loadManifest is not called again.
if let haveToken = SessionManager.shared.currentUser?.token
{
$0.headers["Authorization"] = haveToken
}
}
That token injection part is already working well, by the way. Yay, Siesta!
URLSession version
This is bloated compared to Siesta, and I'm now not using this but here is what it used to be:
func login(user: SessionUser, endpoint: URL)
{
DDLogInfo("Logging in: \(user.name) with \(user.password)")
let json: [String: Any] = ["name": user.name, "password": user.password]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.httpBody = jsonData
_currentStatus = .Unknown
weak var welf = self
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
handleLogin(error: error, message: "No data from login attempt")
return
}
let jsonData:Any
do {
jsonData = try JSONSerialization.jsonObject(with: data, options: [])
}
catch let jsonDecodeError {
handleLogin(error: jsonDecodeError, message: "Could not get JSON from login response data")
return
}
guard let jsonDecoded = jsonData as? [String: Any] else {
handleLogin(error: error, message: "Could not decode JSON as dictionary")
return
}
guard let token = jsonDecoded["token"] as? String else {
handleLogin(error: error, message: "No auth token in login response")
return
}
let newUser = SessionUser(name: user.name, password: "", token: token)
welf?.currentUser = newUser
welf?.saveCurrentSession()
welf?._currentStatus = .LoggedIn
DDLogInfo("User \(newUser.name) logged in")
loginUpdate(user: newUser, status: .LoggedIn, message: nil, error: nil)
}
task.resume()
}
Siesta Version
Here is my attempt right now:
func login(user: String, pass: String, status: #escaping (String?) -> ())
{
let json = [ "name": user, "password": pass]
let req = ManifestCloud.shared.keys.request(.post, json: json)
req.onSuccess { (tokenInfo) in
if let token = tokenInfo.jsonDict["token"] as? String
{
let newUser = SessionUser(name: user, password: pass, token: token)
self.currentUser = newUser
}
status("success")
}
req.onFailure { (error) in
status(error.userMessage)
}
req.onCompletion { (response) in
status(nil)
}
}
Its sort of working, but the log in credentials are not saved by Siesta and I've had to rig up a new notification system for login state which I'd hoped Siesta would do for me.
I want to use Siesta's caching so that the SessionUser object is cached locally and I can use it to get a new token, if required, using the cached credentials. At the moment I have a jury-rigged system using UserDefaults.
Any help appreciated!
The basic problem here is that you are requesting but not loading the resource. Siesta draws a distinction between those two things: the first is essentially a fancied-up URLSession request; the second means that Siesta hangs on to some state and notifies observers about it.
Funny thing, I just answered a different but related question about this a few minutes ago! You might find that answer a helpful starting point.
In your case, the problem is here:
let req = ManifestCloud.shared.keys.request(.post, json: json)
That .request(…) means that only your request hooks (onSuccess etc.) receive a notification when your POST request finishes, and Siesta doesn’t keep the state around for others to observe.
You would normally accomplish that by using .load(); however, that creates a GET request and you need a POST. You probably want to promote your POST to be a full-fledge load request like this:
let keysResource = ManifestCloud.shared.keys
let req = keysResource.load(using:
keysResource.request(.post, json: json))
This will take whatever that POST request returns and make it the (observable) latestData of ManifestCloud.shared.keys, which should give you the “notification system for login state” that you’re looking for.
i know this is very basic question that i am gonna ask but i need to ask how can i access data which is dictionary that is getting from server.
here is my response
JSON: {
message = "The email must be a valid email address.";}
now i want to do "po" in console log so what to write after in po statement
Thanks
All you need type
po yourResponseAsDictionary["message"]
UPDATED
Sorry I was thinking you already converted it.
If you are not using anything like SwiftyJSON or ObjectMapper to parse your json data then you can do just the following.
But I would recommend to use some lib to convert response directly to your model
let yourResponseFromNetwork = "{ \"country_code\" : \"+9\", \"user_id\" : 123456}"
if let data = yourResponseFromNetwork.data(using: String.Encoding.utf8) {
do {
if let dic = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] {
let countryCode = dic["country_code"]
let userId = dic["user_id"]
}
} catch {
print("Error occurred")
}
}
I have a Swift App, which implements the web login for Auth0. On successful login, i receive an access token and and idToken, which i both store locally in my Keychain. Upon next login, i first check, if the access token (stored in keychain) is still valid by
Auth0
.authentication()
.userInfo(withAccessToken: accessToken)
.start { result in
switch result {
case .success(result: let profile):
print("access token still valid")
On success does not have to login again. The issue i'm having however, is, that my idToken might be expired already, even though my access token is still valid, which leads to errors when i request my (GraphQL) backend with this idToken. So how do i solve this? Is there a way to check, if my idToken has expired in Swift? Because if there is no way to check, i would have to ask for login, even with potentially not expired tokens. That wouldn't make sense.
An idToken is a JSON Web Token (JWT), so the metadata is readable. To see what this looks like, paste your token into jwt.io and see what the format is.
For example, take this JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMTUwNDc1MzQ0NSIsImFkbWluIjp0cnVlfQ.ggeW2vGcdWKNWmICRfTZ8qcBOQlu38DzaO8t_6aNuHQ
It is broken into 3 parts: the header, the payload, and the signature. The expiration is in the payload section.
We just need to base64 decode this.
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvbiBTbm93IiwiZXhwIjoiMTUwNDc1MzQ0NSIsImFkbWluIjp0cnVlfQ.aCiqyVAAmHizPshrcdy8jwgHvBg4Diz2YY2e1INjoPg"
// get the payload part of it
var payload64 = jwt.components(separatedBy: ".")[1]
// need to pad the string with = to make it divisible by 4,
// otherwise Data won't be able to decode it
while payload64.count % 4 != 0 {
payload64 += "="
}
print("base64 encoded payload: \(payload64)")
let payloadData = Data(base64Encoded: payload64,
options:.ignoreUnknownCharacters)!
let payload = String(data: payloadData, encoding: .utf8)!
print(payload)
This prints out:
{"sub":"1234567890","name":"Jon Snow","exp":"1504753445","admin":true}
The exp is your expiration date. You can pass this off to a JSON serializer to get the date:
let json = try! JSONSerialization.jsonObject(with: payloadData, options: []) as! [String:Any]
let exp = json["exp"] as! Int
let expDate = Date(timeIntervalSince1970: TimeInterval(exp))
let isValid = expDate.compare(Date()) == .orderedDescending
What I want to do is sync the data from google calendar to my app.
I can login my app with google+ account now, then how can I get the private JSON data from calendar with my account ID and password?
Thanks in advance.
I was able to retrieve JSON data via HTTP requests using Google Calendar API. As an example, here is a code snippet to make a request to retrieve my calendar metadata:
let url = NSURL(string: "https://www.googleapis.com/calendar/v3/calendars/calendarId")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let dataAsNSString = NSString(data: data, encoding: NSUTF8StringEncoding)
// implement your app logic
}
task.resume()
As far as syncing data between your app and Google Calendar, you can implement callbacks to make PUT requests to update via Google Calendar API. As an example, here is a code snippet for a callback function to make a PUT request to sync w/ your Google Calendar:
func updateUserDataToGoogleCalendar(calendarId: String) {
let url = NSURL(string:"https://www.googleapis.com/calendar/v3/calendars/calendarId")
let dataAsString = "sync data"
var data: NSData? = dataAsString.dataUsingEncoding(NSUTF8StringEncoding)
var headers = Dictionary<String, String>() // specify any headers
Http().put(url!, headers: headers, data:data!) { (result) in
if result.success {
if let json: AnyObject = result.jsonObject {
// implement your app logic
}
}
}
There are numerous Swift HTTP wrapper libraries available. In my example, I am using Swift HTTP.