12

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:

OIDAuthState does not have refresh 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

fiks
  • 965
  • 7
  • 20
  • How are you making the initial auth request? Are you using AppAuth for that? – Tom Harrington Nov 01 '17 at 15:10
  • @TomHarrington No, I am not making any initial auth request, since this is a "Sign up" endpoint, that should be invoked without any authorization. Otherwise I might be missing something – fiks Nov 01 '17 at 15:28
  • How do you get the initial token that you want to refresh then? – Tom Harrington Nov 01 '17 at 15:38
  • Well, when I do the sign up request, I send the user's info (email, pwd, etc.), then the server creates a new token info (access token, refresh token, etc.) and sends it back to the client. I have mentioned it in my question. So the access token is saved by AppAuth (see the manual code I wrote) and everything works fine, but after the access token expires, the AppAuth is not able to refresh it, because it's not able to find the refresh token (the one received during sign up). – fiks Nov 01 '17 at 15:53
  • Looking at the documentation for 'OIDAuthorizationResponse' refresh token is not a main member. Maybe 'OIDAuthState' does not know how to pull this out of the additional parameters. Try setting it manually. – Brian from state farm Nov 15 '17 at 13:52
  • It is a readonly property. If I don't find a better answer, I will probably make it read-write. – fiks Nov 15 '17 at 14:21
  • Turn all the strings in the configuration lowercase. – nckbrz Nov 22 '17 at 12:26

4 Answers4

5

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!]
       )
BuLB JoBs
  • 791
  • 3
  • 18
  • 1
    If the access token expires, then I should just use the refresh token to get a new access token. That's the whole purpose of the refresh token, so that the user does not have to log in again. – fiks Nov 04 '17 at 13:42
  • So you have call new webservice for new token! – BuLB JoBs Nov 04 '17 at 14:35
  • that's why I use AppAuth and it automatically calls web service for a new token, but the problem is that it's not able to make a call, because it's not able to deserialize the refresh token, because it's null. So when I initially save the `OIDAuthState`, it does not save the refresh token. – fiks Nov 04 '17 at 14:38
  • I want to confirm. Are you get 'null' data it's about your new web service or old token? – BuLB JoBs Nov 04 '17 at 20:43
  • I am not getting null refresh token from the service. It's somehow not initialized by AppAuth, so when AppAuth tries to refresh the token, it does nothing, because it's now able to find it. See the image I've attached. – fiks Nov 04 '17 at 20:48
  • Can you elaborate more on sign out? since we are not getting what should be implemented for sign out. – Ganesh G Oct 05 '18 at 20:18
2

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.

  1. 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).
  2. Send this access token to your server as soon as the user logs in via Google. Autheticate this token on server-side.
  3. After the access token is authenticated, send your own unique token from your server (with an expiry time or not) to the user.
  4. The user continues app usage with this unique token you've provided.
Shivansh Jagga
  • 1,141
  • 1
  • 8
  • 17
  • 1
    I am doing exactly the same when a user logs in with FB. However, as I wrote in my questions, I have my own implementation of OAuth2 server(it's Spring boot realization). And it allows me to do exactly the same what you have described. In my case, my OAuth2 server behaves exactly the same as any other OAuth2 provider(FB ,Google, etc.) and it works perfectly fine with log in endpoint. However, when the user signs up, there is no API in AppAuth to save the token manually, so I am doing it with my own code and that does not work. – fiks Nov 22 '17 at 14:13
1

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)
coniferous
  • 137
  • 3
  • 8
  • Were you getting Refresh token through this "performActionWithFreshTokens" method? or any other method you were invoking? Can you please provide some code snippet here. – Ganesh G Oct 04 '18 at 20:51
0

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.

nckbrz
  • 688
  • 1
  • 6
  • 19