redirect uri causing issues when navigating to an oauth url - swift

I am trying to navigate to the Microsoft Live oauth authentication page so that I can authorize my user and get a token for use by the app. When I use the following NSURL string, I am able to navigate to the site and authorize my app, retrieving the token.
let stringUrl = "https://login.live.com/oauth20_authorize.srf?client_id=\(clientId)&scope=\(scope)&response_type=code"
let url = NSURL(string: stringUrl)!
However, I want to redirect back to my app (using SFSafariViewController). In order to do so, I added a URL Scheme to my app, and passed that in as the redirect_uri in the URL.
let stringUrl = "https://login.live.com/oauth20_authorize.srf?client_id=\(clientId)&scope=\(scope)&response_type=code&redirect_uri=Lifestream://onedrive-oauth-callback"
let url = NSURL(string: stringUrl)!
However the Live login site gives me an error saying something went wrong and it couldn't continue. This error occurs before the oauth login page is displayed. It happens immediately upon navigating to the URL.
Am I creating my URL scheme incorrectly, or passing the scheme in to the redirect uri improperly? I'm confused as to why it works fine without the redirect_uri, but the site can't load when I provide it.
Can someone point me in the right direction on how I am supposed to pass a redirect url for my app, into an oauth redirect?
Update
It seems that Microsoft does not allow you to register a redirect URL that is a App URL scheme. I dont know how to get this info back into my app then, other than just paying for a site I can point MSFT to, which would then do nothing but redirects into the app for me.

I had a similar OAuth problem and didn't want to mess around with a web server so instead what I did was kinda cheeky. Make a UIWebView and put the request on the web view. Then delegate it to yourself and pass the redirect URL to be http://localhost:8000 (it can be anything like that it really doesn't matter). Then inside the delegate do this:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
{
if let URL = request.URL?.absoluteString
{
// This should be the redirect URL that you pass it can be anything like local host mentioned above.
if URL.hasPrefix(redirectURL)
{
// Now you can simply do some string manipulation to pull out the relevant components.
// I'm not sure what sort of token or how you get it back but assuming the redirect URL is
// YourRedirectURL&code=ACCESS_TOKEN and you want access token heres how you would get it.
var code : String?
if let URLParams = request.URL?.query?.componentsSeparatedByString("&")
{
for param in URLParams
{
let keyValue = param.componentsSeparatedByString("=")
let key = keyValue.first
if key == "code"
{
code = keyValue.last
}
}
}
// Here if code != nil then it has the ACCESS_TOKEN and you are done! If its nil something went wrong.
return false // So that the webview doesnt redirect to the dummy URL you passed.
}
}
return true
}
This is sorta hacky but it works great and you don't need any server nor do you need a redirect URI on your app, its an awesome way to do it in my opinion. You could optimize this for swift 2 to reduce the indentation by using guards but I wrote this before it came out so...
Hope this helps!

Related

URL Schemes not working on macOS

I'm working on an app that needs to get an authorization token from an external provider.
So, I need a custom URL scheme for the redirection callback.
The redirection callback is: chirper://success.
I registered the URL Scheme in my Info.plist:
I also added the following method in my AppDelegate.swift:
func handleGetURLEvent(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
if let aeEventDescriptor = event?.paramDescriptor(forKeyword: AEKeyword(keyDirectObject)) {
if let urlStr = aeEventDescriptor.stringValue {
let url = URL(string: urlStr)
print(url)
// do something with the URL
}
}
}
But when I open the redirection callback URL with Safari, this is what I get:
Safari can't open this URL because macOS doesn't recognize URLs that start with chirper:
Try to "Clean Build Folder" and rebuild. Did help for me. Looks like this is required in some cases.
According to the answer to this question, the problem is the inclusion of :// in the callback URL. If you remove those, the URL should be opened. I found this was correct on macOS 10.15.5.

CloudKit CKShare URL Goes Nowhere

I have successfully saved a CKShare URL to CloudKit, and I can see that the user is INVITED in the CloudKit Dashboard. My Mac app emailed the URL to that person, but when they click it, all they see it this screen on icloud.com:
Clicking OK makes everything disappear so all you see is the background on the web page.
My understanding is that the URL is supposed to open my Mac app where it will fire userDidAcceptCloudKitShareWith in my app delegate. But it does nothing.
Could this be because my app is in development and not in the Mac App Store yet? Do I need a custom URL scheme to get it to open my app?
Documentation on this stuff is pretty sparse. I'd love any help someone can provide.
I have since learned that you must specify a fallback URL for your CloudKit container. In cases where the app isn't installed (or isn't recognized, which seems to be the case when doing dev builds in Xcode like I am), CloudKit will forward share URL to somewhere you specify. They append the unique share ID to the URL so that you can process it on your own web page.
In the CloudKit dashboard, go to Environment Settings... and you'll see this popup:
I have it redirect to https://myapp.com/share/?id= and on my web page where it redirects to, I do a $_GET['id'] to grab the id. I then do another redirect to my application using a custom URL scheme and pass the share ID (e.g. myapp://abc123 where abc123 is the share ID).
In my app delegate, I receive the URL like this:
func application(_ application: NSApplication, open urls: [URL]) {
if let url = urls.first, let shareId = url.host{
fetchShare(shareId) //<-- sharedId = abc123
}
}
I then use CKFetchShareMetadataOperation to look up the URL of the share and CKAcceptSharesOperation to accept it like this:
func fetchShare(shareId: String){
if let url = URL(string: "https://www.icloud.com/share/\(shareId)"){
let operation = CKFetchShareMetadataOperation(shareURLs: [url])
operation.perShareMetadataBlock = { url, metadata, error in
if let metadata = metadata{
//:::
acceptShare(metadata: metadata)
}
}
operation.fetchShareMetadataCompletionBlock = { error in
if let error = error{
print("fetch Share error: \(error)")
}
}
CKContainer.default().add(operation)
}
}
func acceptShare(metadata: CKShareMetadata){
let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])
operation.acceptSharesCompletionBlock = { error in
if let error = error{
print("accept share error: \(error)")
}else{
//Share accepted!
}
}
CKContainer.default().add(operation)
}
I think there are easier ways to work through this using NSItemProvider and NSSharingService, but I'm doing a lot of custom UI and wanted to have full control of the share workflow.
I hope this helps someone. :)

Is it a good practice to reload credentials before every request?

I'm working on an iOS app that works with a backend made in laravel, this api manage login and information exchange via JSON web tokens.
To prevent the user to access a web route with an expired token I check the user's credentials before every web call.
My question is, is it a good practice? Because when I started thinking about it the user is accessing twice the amount of times to my server.
For example this is the function to access they're information.
/// Get user's info form the database
///
/// - Parameter completed: Completition Block
func getInfo(completed: #escaping DownloadComplete){
let token = self.getToken()
print(self.getError())
if(token != nil && token != ""){
//Here is where I check credentials.
self.checkCredentials(completed: {
let url = "\(Base_URL)\(myInfo)\(self.getToken() ?? "")"
Alamofire.request(url, method: HTTPMethod.get).responseJSON { respone in
if let result = respone.result.value as? Dictionary<String, AnyObject>{
if let user = result["User"] as? Dictionary<String, AnyObject>{
var Info = Alumno()
if let id = user["id"] as? Int64!{
Info.id = id
}
...
self.userInfo = Info
completed()
}
}
}
})
}
}
The check credentials functions asks the server if the user's token is still valid and if it is it returns
{
"Status": "Success"
}
My answer assumes your server validates the token, and you are not relying on the app to validate the token.
With that assumption, I think it depends on what you can do if the token is expired.
If the only thing you can do is prompt the user to sign in again, then I would probably just make the web request and handle the 401 Not Authorized.
However, if you have the ability to silently refresh the user's token, you may want to preemptively refresh the token if is close to expiring.
In my application, before every web request the app checks if the user's access token will expire within the next 3 minutes, and if so, attempts to refresh it before making the request. If the refresh is successful, the web request is made with the new access token. If the refresh fails, the web request is not made and the app prompts the user to sign in instead. If for any reason the web request still fails after that with 401, the app prompts the user to sign in.
I would do it like once every time the user opens the app not every call.
Other options:
You could also give them a code that they enter that's good for so long.
You could have them sign in when they first download the app. Then they would always be logged in. Then when the token is expired a little popup would say "Something Something Something"

iOS Today Widget utilize Uber login token

I have an app with an Uber login that gives access to restricted API calls (info on the current ride). I'd like to share the login token with the associated Today Widget so it can make similar calls.
I'm already sharing data with a UserDefaults suite, and I'm using the UberRides SDK. In digging into the RidesClient object it seems to try to use the keychain for storing/sharing the login token, and I set up a shared keychain to try to take advantage of this, but no luck. Restricted API calls from the widget return as unauthorized. Any suggestions?
Here's some code from the widget (note the user already authenticated in the main app):
let rc = RidesClient()
rc.fetchCurrentRide { ride, response in
if ride == nil { print("NO CURRENT RIDE") }
print(response.response)
print(response.error?.title)
if let ride = ride {
// do something
} else {
self.ride = nil
}
}
This returns an unauthorized response. I traced into the RidesClient (which is an object in the UberRides SDK), and see the code where the token is "supposed" to come from the keychain, but it doesn't.
I also tried generating my own URL request in the widget, using the login token passed through shared UserDefaults. This followed the standard HTTP access approach, putting the token in the Authorization header. But I got the same unauthorized response.
Here's some more details on the SDK approach:
Main app uses the LoginButton in native mode:
let scopes: [RidesScope] = [.Profile, .Places, .Request, .AllTrips]
let loginManager = LoginManager(accessTokenIdentifier: Configuration.getDefaultAccessTokenIdentifier(), keychainAccessGroup: "com.MYCOMPANY.MYAPP.share", loginType: .native)
let loginButton = LoginButton(frame: loginFrame, scopes: scopes, loginManager: loginManager)
loginButton.presentingViewController = self
loginButton.delegate = self
view.addSubview(loginButton)
The login button does the right thing and authorizes in the Uber app. I can see the token returned in the delegate callback didCompleteLoginWithToken. However, I can then check for the token:
let token = TokenManager.fetchToken(Configuration.getDefaultAccessTokenIdentifier(), accessGroup: "com.MYCOMPANY.MYAPP.share")
print(token)
The token is "nil". I don't think the SDK is saving the token into the access group keychain.
When I use the default keychain (not the keychainAccessGroup), the login in the app works fine and I can get the login token back and make restricted calls to the API. However, that doesn't help the widget, which needs the token from the access group keychain.
Solved!! After many hours of debugging, and searching. What was not clear in ANY documentation is the keychainAccessGroup MUST include the AppIndentifierPrefix. That's the 10 character identifier associated with the App ID. So, instead of using "com.MYCOMPANY.MYAPP.share", it's "APPID.com.MYCOMPANY.MYAPP.share" for the keychainAccessGroup.

Facebook profile pic URL redirect to different URL

I am using Faceboook API to login into my website through FB.
In this when I try to fetch the profile photo of the logged-in user through URL "http://graph.facebook.com/100003373201743/picture". Didn't get the response in code.
*id of user fetched from FB # run time.
Found the reason as well, coz defined URL redirects to "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-frc3/t1.0-1/p50x50/1975108_483512481771188_559759638979699650_s.jpg". I am not finding any link between these two URLs. So that I can directly hit to redirected one URL.
You can directly use the image url as "http://graph.facebook.com/100003373201743/picture", as #Tobi has mentioned
or,
if you want to fetch the actual url, you can get that in response of this-
http://graph.facebook.com/100003373201743/picture?redirect=0
You'll get the response as-
{
data: {
url: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-frc3/t1.0-1/p50x50/1975108_483512481771188_559759638979699650_s.jpg",
is_silhouette: false
}
}
I found the solution of this problem.
Fetching headers of the URL "http://graph.facebook.com/100003373201743/picture".
Header named as "Location" have the value of redirected URL like "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-frc3/t1.0-1/p50x50/1975108_483512481771188_559759638979699650_s.jpg"
So hitting this URL and its working fine.