Once the token is received, when the token is over, then how can I call the token automatically after the login? on same page
Alamofire.request(urlString, method: .post, parameters: newPost, encoding: JSONEncoding.default)
.responseJSON { response in
if let json = response.result.value as? [String : Any]{
print("JSON: \(json)")
if UserDefaults.standard.bool(forKey: "logged_in") {
Token = json["Token"]! as! String
UserDefaults.standard.set(Token, forKey: "Token")
UserDefaults.standard.synchronize()
}
} else {
print("Did not receive json")
}
//expectation.fulfill()
}
For the Authorisation Token, the ideal practice is from server side they need to check, requested API call have TOKEN is valid or not. And if the token is not matched or expired, they will provide HTTP status code 401, from Mobile side you need to check the status code first and if you found 401 you need to forcefully logout or re login which takes a new token and you can save it in your UserDefaults.
Scenario 1 : You need to tell to backend developer who made your webservice, that he need to check if TOKEN is valid or not. if token is expired he need to give message code or message that "Token has been expired" and you can check in Response if message code is for expired than you need to navigate your Login screen.
This is best practice.
Scenario 2 : If you dont want to Logout from app, and keep app going with new token automatically refresh, tell webservice developer that whenever token will be expired he will return new Token in response field as "Authorization" And from your code side, you need to check in each request whether Authorization contains new token.. if it contains that means you need to replace old token with New one in userdefault.
Below is my code in Swift3 :
func requestApiCall(_ urlString: String, paramData: NSObject, completionHandler: #escaping (NSDictionary?, NSError?) -> ()) {
let token = UserDefaults.standard.object(forKey: βtokenβ as String)
var headersVal = [
"Authorization": "Bearer "+(token as String),
]
Alamofire.request(urlString, method: .post, parameters: paramData as? [String : AnyObject],encoding: JSONEncoding.default, headers: headersVal)
.responseJSON { response in
if let authorization = response.response?.allHeaderFields["Authorization"] as? String {
var newToken : String = authorization
UserDefaults.standard.set(newToken, forKey: "token")
UserDefaults.standard.synchronize()
}
switch response.result {
case .success(let value):
if let res = response.result.value {
let response = res as! NSDictionary
let message = response.object(forKey: "message")!
print(message)
if message as! String == "Token has been expired"
{
self.showLoginScreen()
}
}
completionHandler(value as? NSDictionary, nil)
case .failure(let error):
if error._code == -1001 {
print("timeout")
}
completionHandler(nil, nil)
}
}
}
Call service for new token when token expires is unsecure to your app because if token expires and you call service for new token then anyone can access your app or its data. The better way is to logout/sign out the user and ask him to login again.
but if you want to do so then make a method which is user to get new token and call it once you will get token expire code.
but you need to discuss with your backend developer for this new api and accordingly you can move farther
let me know for help . Thanks
I preferred to use Moya framework (abstraction under Alamofire) with PromiseKit. It makes easier write async code and create dependencies between parts. I wrapped all business logic for sending requests in the class. The main function of the request (public func register(device: String, type: String) -> Promise<Device>) returns Promise object. In the recover block I validate error code and if needed I refresh token and repeat failed request. Code example below:
public protocol HttpClientInterface {
...
func register(device: String, type: String) -> Promise<Device>
...
}
public func register(device: String, type: String) -> Promise<Device> {
return self.sendRegisterRequest(with: device, type: type)
.then {
self.parseRegister(response: $0)
}
.recover { lerror -> Promise<Device> in
guard let error = lerror as? HttpClientError,
case .server(let value) = error,
value == ServerError.notAuthenticated,
let refreshToken = self.credentialsStore.get()?.token?.refreshToken else {
throw lerror
}
return self.sendRefreshSessionRequest(operatorId: self.operatorId, refreshToken: refreshToken)
.then { data -> Promise<AuthToken> in
return self.parseRefreshSession(data)
}
.then { token -> Promise<Device> in
self.credentialsStore.update(token: token)
return self.register(device: device, type: type)
}
.catch { error in
if let myError = error as? HttpClientError,
case .server(let value) = httpError,
value == ServerError.notAuthenticated {
self.credentialsStore.accessDenied()
}
}
}
}
func sendRegisterRequest(with name: String, type: String) -> Promise<Data> {
return Promise { fulfill, reject in
self.client.request(.register(device: name, type: type)) {
self.obtain(result: $0,
successStatusCodes: [200, 201],
fulfill: fulfill,
reject: reject)
}
}
}
func parseRegister(response data: Data) -> Promise<Device> {
return Promise { fulfill, reject in
if let account = Account(data: data),
let device = account.devices?.last {
fulfill(device)
} else {
reject(HttpClientError.internalError())
}
}
}
Related
I'm working on building a networking client for my iOS application which uses OAuth 2.0 Authorization techniques (Access & Refresh Token). There is a feature for my networking client that I have been struggling to implement:
When a 401 error occurs that means the Access Token has expired and I need to send a Refresh Token over to my server to obtain a new Access Token.
After getting a new Access Token I need to redo the previous request that got the 401 error.
So far I have written this code for my networking client:
typealias NetworkCompletion = Result<(Data, URLResponse), FRNetworkingError>
/// I am using a custom result type to support just an Error and not a Type object for success
enum NetworkResponseResult<Error> {
case success
case failure(Error)
}
class FRNetworking: FRNetworkingProtocol {
fileprivate func handleNetworkResponse(_ response: HTTPURLResponse) -> NetworkResponseResult<Error> {
switch response.statusCode {
case 200...299: return .success
case 401: return .failure(FRNetworkingError.invalidAuthToken)
case 403: return .failure(FRNetworkingError.forbidden)
case 404...500: return .failure(FRNetworkingError.authenticationError)
case 501...599: return .failure(FRNetworkingError.badRequest)
default: return .failure(FRNetworkingError.requestFailed)
}
}
func request(using session: URLSession = URLSession.shared, _ endpoint: Endpoint, completion: #escaping(NetworkCompletion) -> Void) {
do {
try session.dataTask(with: endpoint.request(), completionHandler: { (data, response, error) in
if let error = error {
print("Unable to request data \(error)")
// Invoke completion for error
completion(.failure(.unknownError))
} else if let data = data, let response = response {
// Passing Data and Response into completion for parsing in ViewModels
completion(.success((data, response)))
}
}).resume()
} catch {
print("Failed to execute request", error)
completion(.failure(.requestFailed))
}
}
}
Endpoint is just a struct that builds a URLRequest:
struct Endpoint {
let path: String
let method: HTTPMethod
let parameters: Parameters?
let queryItems: [URLQueryItem]?
let requiresAuthentication: Bool
var url: URL? {
var components = URLComponents()
components.scheme = "http"
components.host = "127.0.0.1"
components.port = 8000
components.path = "/api\(path)"
components.queryItems = queryItems
return components.url
}
func request() throws -> URLRequest {
/// Creates a request based on the variables per struct
}
}
Where do I put the code that allows the FRNetworking.request() to get a new token and retry the request?
I have done the following inside the else if let data = data, let response = response statement:
if let response = response as? HTTPURLResponse {
let result = self.handleNetworkResponse(response)
switch result {
case .failure(FRNetworkingError.invalidAuthToken):
break
// TODO: Get new Access Token and refresh?
default:
break
}
}
Is this the right approach to refresh the token and redo the API call or is there a better way?
You have to write a function that updates the token and, depending on the result, returns true or false
private func refreshAccessToken(completion: #escaping (Bool) -> Void {
// Make a request to refresh the access token
// Update the accessToken and refreshToken variables when the request is completed
// Call completion(true) if the request was successful, completion(false) otherwise
}
Declare 2 variables at the beginning of the class
var session: URLSession
var endpoint: Endpoint
Inside the case .failure assign these variables
session = session
endpoint = endpoint
Then call refreshAccessToken method. The final code will look like this
if let response = response as? HTTPURLResponse {
let result = self.handleNetworkResponse(response)
switch result {
case .failure(FRNetworkingError.invalidAuthToken):
session = session
endpoint = endpoint
self?.refreshAccessToken { success in
if success {
self?.request(using: session, endpoint, completion: completion)
} else {
completion(.failure(.unknownError))
}
}
break
default:
break
}
}
Currently learning how to add OAuth2 via Alamofire and getting confused. I'm using the password grant type and when a request fails I understand that the retrier kicks in and requests a token refresh. The confusing part is which one do I use?
Alamofire 4 using p2/OAuth2
Alamofire RequestRetrier + Request Adapter
The first one uses less code so unsure if its all the functionality I need. I also can't find a concrete example explaining this process.
I believe the following performs the refresh request?
private func refreshTokens(completion: RefreshCompletion) {
guard !isRefreshing else { return }
isRefreshing = true
let urlString = "\(baseURLString)/oauth2/token"
let parameters: [String: Any] = [
"access_token": accessToken,
"refresh_token": refreshToken,
"client_id": clientID,
"grant_type": "refresh_token"
]
sessionManager.request(urlString, withMethod: .post, parameters: parameters, encoding: .json).responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if let json = response.result.value as? [String: String] {
completion(true, json["access_token"], json["refresh_token"])
} else {
completion(false, nil, nil)
}
strongSelf.isRefreshing = false
}
}
This is then passed back to adapt the previous request?
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {}
Is this the correct way of implementing this?
Thanks
I have an app which stores user data (mail and password) using UserDefaults. Once the user sends this information, next time he/she opens the app, the app should not show the registration view (as long as the user data is recorded).
I think this could be done with a Session but how can I tell the app to show the registration view only once (or when change in information is required)? An example of this could be the Facebook App: you have to log in, and when you open the App again everything is already loaded (main screen).
My app sends the information to a domain through PHP, so maybe a PHP session would help.
You can do the logic in app didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLauchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let email = UserDefaults.standard.value(forKey: "email"), let password = UserDefaults.standard.value(forKey: "password") else {
// Not set up yet
showRegistration()
return true
}
// Already signing up
showLoginView()
return true
}
There are several types of Authentication specially when you are dealing with mobile apps and APIs then you are dealing with tokens.
This is how it usually goes on:
You send your username & password with your clientSecret & clientId that are created for your app by the API developer or owner to connect with it to the server. Example Code:
func loginUser(email:String, pass:String, completion:#escaping (_ status:Bool, _ error:Error?, _ msg:String?) ->())
{
//Get Api keys from plist file then send login request
helper.getApiKeys { (id, key) in
let parameters = [
"grant_type":"password",
"client_id":id,
"client_secret":key,
"username":email,
"password":pass,
"scope":""
] as [String : Any]
let header = [
"Content-Type":"application/json"
]
Alamofire.request(loginUserURL!, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: header)
.validate(statusCode: 200..<300)
.responseJSON { (response) in
switch response.result {
case .success( _):
let json = response.result.value!
let swiftyJson = JSON(json)
guard let expires = swiftyJson["expires_in"].double else {
return
}
let accessToken = swiftyJson["access_token"].stringValue
let refreshToken = swiftyJson["refresh_token"].stringValue
self.helper.setToken(expires: expires, aToken: accessToken, rToken: refreshToken)
completion(true, nil, nil)
case .failure(let err):
//print(err)
if response.response?.statusCode == 401 {
completion(false,err,"Invalid Username or Password")
} else {
completion(false, err, "Error Logging In")
}
}
}
}
}
You receive in the closure function i built an "access token", "refresh token" and an "expiration time"
You save the tokens and the expiration time using UserDefaults in your app and segue the user to the logged in page. Example code:
//Save token in userdefaults
func setToken(expires:Double, aToken:String, rToken:String) {
//Change to expiry unix timeinterval date
let expiryTime = Date().timeIntervalSince1970 + expires
let userDefaults = UserDefaults.standard
userDefaults.set(expiryTime, forKey: "expires_in")
userDefaults.set(aToken, forKey: "access_token")
userDefaults.set(rToken, forKey: "refresh_token")
}
Then in every time app comes to the foreground you check if the user is Authenticated. Which means that you will check if there is a token saved in the UserDefaults the expiration time of the token is still valid according to todays timestamp and if it is due to expiration you can refresh your token. Example code:
//Get tokens
private func getTokens (completion:(_ expires:TimeInterval?, _ accessToken:String?, _ refreshToken: String?) ->())
{
let userDefaults = UserDefaults.standard
let access = userDefaults.string(forKey: "access_token")
let refresh = userDefaults.string(forKey: "refresh_token")
let expiry = userDefaults.double(forKey: "expires_in")
if (access != nil && refresh != nil) {
completion(expiry, access, refresh)
} else {
completion(nil,nil,nil)
}
}
//Check if user is already Authenticated
func isAuth() -> Bool {
var status = false
var apiId = Int()
var apiKey = String()
//Get tokens and test expiry time
getTokens { (expiry, accessToken, refreshToken) in
if (accessToken != nil && refreshToken != nil) {
//If due to expiry then refresh
if ((expiry! - Date().timeIntervalSince1970) < 2592000 /* 1 month */ ) {
print(expiry!); print(Date().timeIntervalSince1970)
getApiKeys(completion: { (id, key) in
apiId = id
apiKey = key
})
refresh(refreshToken: refreshToken!, grantType: "refresh_token", clientId: apiId, clientSecret: apiKey, scope: "", completion: { (stat, err, msg) in
if stat {
status = true
} else {
status = false
}
})
} else {
status = true
}
} else {
status = false
}
}
if status {
return true
} else {
return false
}
}
So this means that the user is always logged in and will never be sent to the login viewController except if isAuth() function returns false. In my case passed the expiry date or the user has just logged out and i deleted his tokens. Example code:
//Delete token for loggin out
func logOut() {
let userDefaults = UserDefaults.standard
userDefaults.removeObject(forKey: "expires_in")
userDefaults.removeObject(forKey: "access_token")
// Segue user to login page
}
I'm using alamofire to get HTTP response from web api. just want to ask how can parse JSON values from response without using swift object mapper frameworks and pass it to authenticatedUser.I have some problem with loadUserData because response return Response type and my function work fine with NSArray how can I converted to work with Response
func GetUserCredential(username:String,password:String)->UserModel
{
let authenticatedUser = UserModel()
let user = username
let password = password
let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(usingCredential: credential)
.responseJSON { response in
return self.loadUserData(response);
}
return authenticatedUser;
}
func loadUserData(response: Response<AnyObject, NSError>) -> UserModel
{
var userObj = UserModel()
for resp as? Response<AnyObject, NSError> in response
{
let user:String = (resp["user"] as! String)
let isAuthenticated:Bool = (resp["authenticated"] as! Bool)
let isManager:Bool = true
userObj = UserModel(username:user,isAuthenticated:isAuthenticated,isManager:isManager)
}
return userObj
}
I believe you can do this with an Alamofire response object.
if let validResponse = response.result.value as? [String : AnyObject] {
return loadUserData(validResponse);
}
I would change your loadUserData to this:
func loadUserData(response: [String : AnyObject]) -> UserModel
// Do everything you're doing in loadUserData
}
I would also do more error checking in your loadUserData method as it's best not to assume that user and authenticated will always exist (your app will crash if they don't or if their values are of different types).
I would also suggest you name GetUserCredential with a lowercase getUserCredential to keep in best practice for naming functions.
My question is quite similar to this one, but for Alamofire : AFNetworking: Handle error globally and repeat request
How to be able to catch globally an error (typically a 401) and handle it before other requests are made (and eventually failed if not managed) ?
I was thinking of chaining a custom response handler, but that's silly to do it on each request of the app.
Maybe subclassing, but which class should i subclass to handle that ?
Handling refresh for 401 responses in an oauth flow is quite complicated given the parallel nature of NSURLSessions. I have spent quite some time building an internal solution that has worked extremely well for us. The following is a very high level extraction of the general idea of how it was implemented.
import Foundation
import Alamofire
public class AuthorizationManager: Manager {
public typealias NetworkSuccessHandler = (AnyObject?) -> Void
public typealias NetworkFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError) -> Void
private typealias CachedTask = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void
private var cachedTasks = Array<CachedTask>()
private var isRefreshing = false
public func startRequest(
method method: Alamofire.Method,
URLString: URLStringConvertible,
parameters: [String: AnyObject]?,
encoding: ParameterEncoding,
success: NetworkSuccessHandler?,
failure: NetworkFailureHandler?) -> Request?
{
let cachedTask: CachedTask = { [weak self] URLResponse, data, error in
guard let strongSelf = self else { return }
if let error = error {
failure?(URLResponse, data, error)
} else {
strongSelf.startRequest(
method: method,
URLString: URLString,
parameters: parameters,
encoding: encoding,
success: success,
failure: failure
)
}
}
if self.isRefreshing {
self.cachedTasks.append(cachedTask)
return nil
}
// Append your auth tokens here to your parameters
let request = self.request(method, URLString, parameters: parameters, encoding: encoding)
request.response { [weak self] request, response, data, error in
guard let strongSelf = self else { return }
if let response = response where response.statusCode == 401 {
strongSelf.cachedTasks.append(cachedTask)
strongSelf.refreshTokens()
return
}
if let error = error {
failure?(response, data, error)
} else {
success?(data)
}
}
return request
}
func refreshTokens() {
self.isRefreshing = true
// Make the refresh call and run the following in the success closure to restart the cached tasks
let cachedTaskCopy = self.cachedTasks
self.cachedTasks.removeAll()
cachedTaskCopy.map { $0(nil, nil, nil) }
self.isRefreshing = false
}
}
The most important thing here to remember is that you don't want to run a refresh call for every 401 that comes back. A large number of requests can be racing at the same time. Therefore, you want to act on the first 401, and queue all the additional requests until the 401 has succeeded. The solution I outlined above does exactly that. Any data task that is started through the startRequest method will automatically get refreshed if it hits a 401.
Some other important things to note here that are not accounted for in this very simplified example are:
Thread-safety
Guaranteed success or failure closure calls
Storing and fetching the oauth tokens
Parsing the response
Casting the parsed response to the appropriate type (generics)
Hopefully this helps shed some light.
Update
We have now released π₯π₯ Alamofire 4.0 π₯π₯ which adds the RequestAdapter and RequestRetrier protocols allowing you to easily build your own authentication system regardless of the authorization implementation details! For more information, please refer to our README which has a complete example of how you could implement on OAuth2 system into your app.
Full Disclosure: The example in the README is only meant to be used as an example. Please please please do NOT just go and copy-paste the code into a production application.
in Alamofire 5 you can use RequestInterceptor
Here is my error handling for 401 error in one of my projects, every requests that I pass the EnvironmentInterceptor to it the func of retry will be called if the request get to error
and also the adapt func can help you to add default value to your requests
struct EnvironmentInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: #escaping (AFResult<URLRequest>) -> Void) {
var adaptedRequest = urlRequest
guard let token = KeychainWrapper.standard.string(forKey: KeychainsKeys.token.rawValue) else {
completion(.success(adaptedRequest))
return
}
adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: HTTPHeaderField.authentication.rawValue)
completion(.success(adaptedRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: #escaping (RetryResult) -> Void) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
//get token
guard let refreshToken = KeychainWrapper.standard.string(forKey: KeychainsKeys.refreshToken.rawValue) else {
completion(.doNotRetryWithError(error))
return
}
APIDriverAcountClient.refreshToken(refreshToken: refreshToken) { res in
switch res {
case .success(let response):
let saveAccessToken: Bool = KeychainWrapper.standard.set(response.accessToken, forKey: KeychainsKeys.token.rawValue)
let saveRefreshToken: Bool = KeychainWrapper.standard.set(response.refreshToken, forKey: KeychainsKeys.refreshToken.rawValue)
let saveUserId: Bool = KeychainWrapper.standard.set(response.userId, forKey: KeychainsKeys.uId.rawValue)
print("is accesstoken saved ?: \(saveAccessToken)")
print("is refreshToken saved ?: \(saveRefreshToken)")
print("is userID saved ?: \(saveUserId)")
completion(.retry)
break
case .failure(let err):
//TODO logout
break
}
}
} else {
completion(.doNotRetry)
}
}
and you can use it like this :
#discardableResult
private static func performRequest<T: Decodable>(route: ApiDriverTrip, decoder: JSONDecoder = JSONDecoder(), completion: #escaping (AFResult<T>)->Void) -> DataRequest {
return AF.request(route, interceptor: EnvironmentInterceptor())
.responseDecodable (decoder: decoder){ (response: DataResponse<T>) in
completion(response.result)
}