Intercept oauth2 callback - ios

I'm trying to connect an iOS Swift app to an API, and I've experimented with oauthswift, aerogear, and heimdallr.
The flow is working fine, but the API itself doesn't have user-owned resources. All users have access to all resources. The API does, however, require OAuth2 to authenticate.
Is there a way to prevent a swift app from bouncing to Safari (or Safariwebview) and either avoiding the user login part or handling it with a workaround? I know this is sort of antithetical to oauth2, but there's no need (and actually it would be an impediment) for a single user to be logged in to this api.
Basically, I want the app to login on the backend for access to every user. I know this api has sdk's in Ruby, Python, and Node that do just that. So, how can I do this in Swift?
Here's my oauthswift code that successfully gets me in:
let yourEndpoint = ("https://www.awebsite.com/search/shows/a_show")
let oauthswift = OAuth2Swift(
consumerKey: "my key",
consumerSecret: "my secret",
authorizeUrl: "https://www.awebsite.com/oauth/authorize",
accessTokenUrl: "https://www.awebsite.com/oauth/token",
responseType: "token")
let name = "sample_api_proj"
oauthswift.authorizeWithCallbackURL( NSURL(string: "xxx:xxxx:xx:oauth:2.0:xxx")!, scope: "", state: "", success: {
credential, response, parameters in
self.showTokenAlert(name, credential: credential)
let parameters = Dictionary<String, AnyObject>()
oauthswift.client.get("https://www.awebsite.com/api/search/shows/ashow", parameters: parameters,
success: {
data, response in
let jsonDict: AnyObject! = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
print(jsonDict)
}, failure: { error in
print(error)
})
}, failure: { error in
print(error.localizedDescription)
})

I'm returning to this to provide the answer that I ultimately found. I didn't realize there were different types of oauth2, and the type used to authorize an entire app is called 'client credentials.' Not all libraries/pods are designed for this type of oauth. The working solution I found was with p2.OAuth2.

Related

Swift OAuth2.0 with redirectURI

I'm using a service that provides an OAuth2.0 authentication. This are the steps i need:
Open a URL with user Id as params
User approves my app (which is correctyle registered).
The user is redirected to a RedirectUri, with access token in the hash.
The third point is my main problem.
I've implemented the OAuth with microsoft libraries and everything works fine. But I cant use them here so I'm trying https://github.com/OAuthSwift/OAuthSwift this one.
This is my code:
private func authenticationService() {
// create an instance and retain it
let oauthswift = OAuth2Swift(
consumerKey: "xx",
consumerSecret: "xxx",
authorizeUrl: "//myurl + userId",
responseType: "token"
)
oauthswift.authorizeURLHandler = OAuthSwiftOpenURLExternally.sharedInstance
let handle = oauthswift.authorize(
withCallbackURL: "???",
scope: "", state:"") { result in
switch result {
case .success(let (credential, response, parameters)):
print(credential.oauthToken)
// Do your request
case .failure(let error):
print(error.localizedDescription)
}
}
}
This open correctly my Safari but then I'm redirected to the URI with access token in the hash and nothing happened.
The main problem here is that I've a redirect uri so I guess the callback URL is not called? And this is not opening a sheet but it is redirecting to Safari. And I dont like this approach.
How can I perform OAuth2.0 in swift with the steps above? How can I get the access token from an url? What is the best library and how can I get the most of it?
Update:
This is my code for stackExchange:
let request: OAuth2Request = .init(authUrl: "https://stackexchange.com/oauth/dialog?client_id=<MYCLIENTID>&scope=private_info&redirect_uri=https://stackexchange.com/oauth/login_success",
tokenUrl: "https://stackoverflow.com/oauth/access_token/json",
clientId: "<MYCLIENTID>",
redirectUri: "https://stackexchange.com/oauth/login_success",
clientSecret: "",
scopes: [])
The OAuth domain in stack apps is => stackexchange.com
So i've added in my URL Types the following: redirect-uri://<stackexchange.com> (even without <>)
But everytimes I approve my app i'm stacked in the "Authorizing application" which contains my token and i'm not redirected.
If you are targeting iOS 13 you can use the new AuthenticationServices library provided by Apple.
It will work on both macOS and iOS.
Maybe this would help other developers, I create a simple and small swift package to handle OAuth2 in Swift, you can check the demo project it works very well 👍
https://github.com/hadiidbouk/SimpleOAuth2
Edit:
You are passing the wrong URLs, they should be like this
let request: OAuth2Request = .init(authUrl: "https://stackoverflow.com/oauth",
tokenUrl: "https://stackoverflow.com/oauth/access_token/json",
clientId: "<<your client id>>",
redirectUri: "redirect-uri://stackexchange.com",
clientSecret: "<<your client secret>>",
scopes: ["private_info"])
In the below code:
private func authenticationService() {
// create an instance and retain it
let oauthswift = OAuth2Swift(...)
you don't retain oauthswift (nor handle). They will be deallocated as soon as you finish executing the authenticationService function.
You need to store references to oauthswift and handle outside the function (at the class level).
let oauthswift: OAuth2Swift
let handle: ...
init() {
oauthswift = OAuth2Swift(
consumerKey: "xx",
consumerSecret: "xxx",
authorizeUrl: "//myurl + userId",
responseType: "token"
)
...
}
private func authenticationService() {
handle = oauthswift.authorize(...)
}

Basecamp3 login issue with Oauth2 in Swift

My app needs to get a basecamp3 login. Hence I used the OAuth2Swift library. But unfortunately, I am unable to receive the token from basecamp even the user has authorized the app.
Below is the screenshot
I have used the following code
func createAuthRequest(){
// create an instance and retain it
let oauthswift = OAuth2Swift(
consumerKey: clientID,
consumerSecret: clientSecret,
authorizeUrl: authURL,
responseType: "token"
)
//oauthswift.authorizeURLHandler = self
oauthswift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthswift)
let handle = oauthswift.authorize(
withCallbackURL: URL(string: redirectURL)!,
scope: "profile", state:"") { result in //This block of code never executed
switch result {
case .success(let (credential, response, parameters)):
print(credential.oauthToken)
// Do your request
case .failure(let error):
print(error.localizedDescription)
}
}
}
The code inside withCallbackURL never executed even the user has authorized the app.
Any help regarding this is appreciated.
I found the solution the problem was I was using wrong authentication & token URL.
Following URL need to be used. I missed to add web_server in auth/token url and unfortunately Basecamp haven't mentioned the same in their documets.
let authURL = "https://launchpad.37signals.com/authorization/new?type=web_server"
let tokenURL = "https://launchpad.37signals.com/authorization/token?type=web_server"
and redirectURL = com.abc.abc:/oauth2Callback (The same redircturl need to be updated for app under basecamp developer console where com.abc.abc is bundle id of the app)

AppAuth iOS. How to save the token info after user registers an account in my own OAuth2 server

I have an iOS app and a backend written in Spring with OAuth2 mechanizm implemented.
I have a signup endpoint in my backend, which accepts some user data and returns an OAuth2 response, e.g.
{
"access_token":"9154140d-b621-4391-9fdd-fbba9e5e4188",
"token_type":"bearer",
"refresh_token":"dd612036-7cc6-4858-a30f-c548fc2a823c",
"expires_in":89,
"scope":"write"
}
After that I want to save OIDAuthState in my keychain, so I do the following:
func saveTokenResponse(responseDict: [String: NSCopying & NSObjectProtocol]) {
let redirectURI = URL(string: kRedirectURI)
let configuration = OIDServiceConfiguration(authorizationEndpoint: URL(string: kAuthorizationEndpoint)!, tokenEndpoint: URL(string: kTokenEndpoint)!)
let request = OIDAuthorizationRequest(configuration: configuration, clientId: self.kClientID, scopes: ["write"], redirectURL: redirectURI!, responseType: OIDResponseTypeCode, additionalParameters: nil)
let accessToken = responseDict["access_token"] ?? nil
let tokenType = responseDict["token_type"] ?? nil
let refreshToken = responseDict["refresh_token"] ?? nil
let expiresIn = responseDict["expires_in"] ?? nil
let scope = responseDict["scope"] ?? nil
let response = OIDAuthorizationResponse(request: request, parameters: [
"access_token": accessToken!,
"token_type": tokenType!,
"refresh_token": refreshToken!,
"expires_in": expiresIn!,
"scope": scope!
]
)
let authState = OIDAuthState(authorizationResponse: response) //here authState.refreshToken is nil, so it won't be serialized.
updateAuthState(authState: authState) // it just saves it in keychain
}
Everything works well, but I have an issue when the token expires.
The app makes a call to the backend and AppAuth is not able to refresh the token:
I can see, that the refresh token is not present in the OIDAuthState object. I have checked the initialization of the OIDAuthState from OIDAuthorizationResponse and found out that token is not assigned in that case.
Can anybody help how I can save OIDAuthState from the OAuth response I receive from my backend? Or am I doing something in a wrong way?
Thanks,
Osman
If your token is expires then you move on login screen (call sign out function) like all app (Banking system, Amazon, Paytm and Facebook). So user when login with your credential then getting same responses like you want and need refresh token. You have get samilar information in login web service like below response. I hope it's work fine
let accessToken = responseDict["access_token"] ?? nil
let tokenType = responseDict["token_type"] ?? nil
let refreshToken = responseDict["refresh_token"] ?? nil
let expiresIn = responseDict["expires_in"] ?? nil
let scope = responseDict["scope"] ?? nil
let response = OIDAuthorizationResponse(request: request, parameters: [
"access_token": accessToken!,
"token_type": tokenType!,
"refresh_token": refreshToken!,
"expires_in": expiresIn!,
"scope": scope!]
)
I don't know what exactly you're doing but I would suggest you to use a different mechanism which is widely used.
Everytime a user logs in with Google, a temporary access token is sent by Google which expires in a short while.
The access token you get on logging in, should be sent to your server just to see and check if the user is authentic and all his personal details can be fetched along with it too (via GoogleAPI call).
Send this access token to your server as soon as the user logs in via Google. Autheticate this token on server-side.
After the access token is authenticated, send your own unique token from your server (with an expiry time or not) to the user.
The user continues app usage with this unique token you've provided.
I ran into this same issue using AppAuth for iOS (refreshToken was null). I was able to get it working by adding adding "offline_access" to my scopes.
let request = OIDAuthorizationRequest(configuration: configuration!, clientId: self.kClientID, scopes: [OIDScopeOpenID, OIDScopeProfile, "api", "offline_access"], redirectURL: redirectURI! as URL, responseType: OIDResponseTypeCode, additionalParameters: nil)
Change the let redirectURI = URL(string: kRedirectURI) string to lowercase, oAuth for ios is picky.
Could also try to use Okta as a middleman service that plugs right in with oAuth.

Outlook OAuth2 can't redirect to application

I'm currently trying use the outlook mail service to obtain contacts/appointments and mails. However I've stumbled upon a problem.
I'm using OAuth2Swift as library to make all my OAuth calls since I'm integrating multiple services.
I created a URL schemelike described in their README
Then I created a Constants file which looks like this
struct Consumer {
let consumerKey: String
let consumerSecret: String
let authorizeURL: String
let accessTokenURL: String
let responseType: String?
let requestTokenURL: String?
}
let Outlook = Consumer(
consumerKey: "",
consumerSecret: "",
authorizeURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
accessTokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
responseType: "code",
requestTokenURL: nil)
I created an application for outlook on https://apps.dev.microsoft.com
generated my key and secret and filled them in inside my application.
I added the mobile application platform to my app. Which tells me to use the redirect URI urn:ietf:wg:oauth:2.0:oob
So my code to authorise looks like this
#IBAction func btn_Outlook(_ sender: Any) {
let oauthOU = OAuth2Swift(
consumerKey: Outlook.consumerKey,
consumerSecret: Outlook.consumerSecret,
authorizeUrl: Outlook.authorizeURL,
accessTokenUrl: Outlook.accessTokenURL,
responseType: Outlook.responseType!)
oauthOU.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthOU)
oauthOU.authorize(
withCallbackURL: "urn:ietf:wg:oauth:2.0:oob",
scope: "https://outlook.office.com/Mail.ReadWrite https://outlook.office.com/Mail.Send https://outlook.office.com/Calendars.ReadWrite https://outlook.office.com/Contacts.ReadWrite https://outlook.office.com/Tasks.ReadWrite",
state: state,
success: { credential, response, parameters in
print("logged in with \(credential), with response \(response) and parameters \(parameters)")},
failure: { error in
print("error occured \(error.localizedDescription)")
}
)
}
when I run the code I first get a screen to enter my mail/password. When I enter my mail it will redirect me to a different page/portal where I can enter my password. When I've entered my password it will show me the permissions screen.
as soon as I hit yes it will give me an error saying "Safari cannot open the page because the address is invalid." I'm pretty sure this has to do with the redirect URI but I'm not sure what to do to actually fix this.
I'm hoping someone is able to help me out with this!
I think you forgot to specify the identifier urn:ietf:wg:oauth:2.0:oob in your URL scheme settings (see first image: there is no identifier set)
I think you forgot to handle redirect URI in AppDelegate from OAuthSwift library! You've to handle the callback in AppDelegate as below.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if (url.host == "oauth-callback") {
OAuthSwift.handle(url: url)
}
return true
}

Twitter OAuth throws 'Desktop applications only support the oauth_callback value oob' on iOS app

So I'm currently trying to get authorization for twitter working by making use of the OAuthSwift plugin I've tried to get it working with both OAuth1 and OAuth2 both unsuccessfully and with different errors/problems.
So for OAuth2 I get this response
Whoa there!
There is no request token for this page. That's the special key we need from applications asking to use your Twitter account. Please go back to the site or application that sent you here and try again; it was probably just a mistake.
I'm using the following piece of code for this
let oauthswift = OAuth2Swift(
consumerKey: Twitter.consumerKey,
consumerSecret: Twitter.consumerSecret,
authorizeUrl: Twitter.authorizeURL,
accessTokenUrl: Twitter.accessTokenURL,
responseType: Twitter.responseType!)
let state = generateState(withLength: 20)
oauthswift.authorize(
withCallbackURL: "https://oauthswift.herokuapp.com/callback/twitter",
scope: "",
state: state,
success: { credential, response, parameters in
print("logged in with \(credential), with response \(response) and parameters \(parameters)")
},
failure: { error in
print("error occured \(error.localizedDescription)")
}
)
Then I moved on to using OAuth1 which gives me the error
Desktop applications only support the auth_callback value 'oob' while I'm obviously trying to do this from an iOS application.
the code I used here is as follows
let oauthTwitter = OAuth1Swift(
consumerKey: "myKey",
consumerSecret: "mySecret",
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token")
oauthswift.authorize(
withCallbackURL: "https://oauthswift.herokuapp.com/callback/twitter",
scope: "",
state: state,
success: { credential, response, parameters in
print("logged in with \(credential), with response \(response) and parameters \(parameters)")
},
failure: { error in
print("error occured \(error.localizedDescription)")
}
)
I'm hoping someone is able to tell me what I'm doing wrong here because I can't seem to figure it out.
The problem ended up to be that I had setup the wrong url scheme. It contained a _ which isn't allowed.
In my case I didn't set a callback URL for the app on Twitter Dev site - https://developer.twitter.com/en/apps

Resources