How to manage OAuth flow in mobile application with server

487

Solution 1

The second approach which you presented is a valid one to implement an OAuth flow. Your backend can act as the confidential client you need, and you can safely obtain tokens from Authorization Servers. This pattern is sometimes referred to as Backand-For-Frontend (though this name is a bit overloaded nowadays). At Curity we described something similar, which we call the Token Handler Pattern. The premise is similar, but our main target are Single Page Applications which can leverage cookies to manage sessions.

Of course the thing you need then is to create a secure session between your backend and the mobile app. In a browser app you would use just use plain old sessions with cookies. You have to make sure that noone can impersonate a user of your app when calling your backend.

Another thing you can explore is whether Stravia supports Dynamic Client Registration standard. Using DCR you can register each instance of your mobile app as a separate client. Then each of those clients receives their own client ID and secret. You then don't have to worry about the secret being stolen.

You can also reach out to Stravia and ask them why they don't support public clients with PKCE and whether they plan to do it. PKCE is an important security OAuth standard and companies should seek to support it.

Solution 2

From what I understand, the main concern here is, you want to avoid hardcoding of client secret.
I am taking keycloak as an example for the authorization server, but this would be same in other authorization server as well since the implementation have to follow the standards
In the authrization servers there are two types of client's one is the
1.Confidential client - These are the one's that require both client-id and client-secret to be passed in your Rest api call
enter image description here

The CURL would be like this, client secret required

curl --location --request POST 'http://localhost:8080/auth/realms/testrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=confidentialclient' \
--data-urlencode 'client_secret=<CLIENT_SECRET_VALUE>' \
--data-urlencode 'code=<AUTHORIZATION_CODE_VALUE>' \
--data-urlencode 'redirect_uri=http://localhost:8080/callback' \
--data-urlencode 'code_verifier=<CODE_VERIFIER_PKCE>'

2.Public Client - If you create clients with this option you won't have to pass the client secret. Only client-id is sufficient while making the API calls. enter image description here

For public clients you won't need to pass client secret, one less problem to worry about

curl --location --request POST 'http://localhost:8080/auth/realms/testrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=publiclcient' \
--data-urlencode 'code=<AUTHORIZATION_CODE_VALUE>' \
--data-urlencode 'redirect_uri=http://localhost:8080/callback' \
--data-urlencode 'code_verifier=<CODE_VERIFIER_PKCE>'

These options would be and should be available in most of the standard authorization servers.

Share:
487
Jaime
Author by

Jaime

Love programming, beer and hanging out with my friends.

Updated on January 03, 2023

Comments

  • Jaime
    Jaime over 1 year

    I am developing an Sports Mobile App with flutter (mobile client) that tracks it's users activity data. After tracking an activity (swimming, running, walking ...) it calls a REST API developed by me (with springboot) passing that activity data with a POST. Then, my user will be able to view the logs of his tracked activities calling the REST API with a GET.

    As I know that my own tracking development isn't as good as Strava, Garmin, Huawei and so on ones, I want to let my app users to connect with their Strava, Garmin and so on accounts to get their activities data, so I need users to authorize my app to get that data using OAuth.

    In a first approach, I have managed to develop all the flow of OAuth with flutter using the Authorization Code Grant. The authorization server login is launched by flutter in a user agent (chrome tab), and once the resource owner has done the login and authorize my flutter app, my flutter app takes the authorization code and the calls to the authorization server to get the tokens . So I can say, that my client is my flutter App. When the oauth flow is done, I send the tokens to my Rest API in order to store them in a database.

    My first idea was to send those tokens to my backend app in order to store them in a database and develop a process that takes those tokens, consult resource servers, parses each resource server json response actifvities to my rest API activity model ones and store in my database. Then, if a resource owner consults its activities calling my Rest API, he would get a response with all the activities (the mobiles app tracked ones + Strava, Garmin, resource servers etc ones stores in my db).

    I have discarded the option to do the call to the resource servers directly from my client and to my rest api when a user pushes a syncronize button and mapping those responses directly in my client because I need the data of those resource servers responses in the backend in order to implement a medal functionality. Further more, Strava, Garmin, etc have limits of usage and I don't want to let my resource owners the hability to push the button the times they want.

    Here is the flow of my first idea:

    Steps:

    1. Client calls the authorization server launching a user agent to an oauth login. In order to make the resource owner login and authorize. The url and the params are hardcoded are hardcoded in my client.

    2. Resource owner logins and authorize client.

    3. Callback is sent with code.

    4. Client captures code of the callback and makes a post to he authorization server to get the tokens. As some authorization servers accept PKCE, I am using PKCE when its possible, to avoid attacks and hardcoding my client secret in my client. Others like Strava's, don't allow PKCE, so I have to hardcode the client secret in my client in order to get the tokens.

    5. Once the tokens are returned to my client, I send them to my rest api and store in a database identifying the tokens resource owner.

    To call the resource server:

    1. One periodic process takes the tokens of each resource owner and updates my database with the activities returned from each resource server.

    2. The resource owner calls the rest api and obtains all the activities.

    The problem to this first idea is that some of the authorization servers allow implementing PKCE (Fitbit) and others use the client secret to create the tokens (Strava). As I need the client secret to get the tokens for some of those authorization servers, I have hardcoded the secrets in the client and that is not secure.

    I know that it is dangerous to insert the client secrets into the client as a hacker can decompile my client and get the client secret. I can't figure how to get the resource owner tokens of Strava without hardcoding the client secret if PKCE is not allowed in the authorization server.

    As I don't want to hardcode my client secrets in my client because it is insafe and I want to store the tokens in my db, I dont see my first approach as a good option. Further more, I am creating a POST request to my REST API in order to store the access token and refresh token in my database and if i am not wrong, that process can be done directly from the backend.

    I am in the situation that I have developed a public client (mobile app) that has hardcoded the client secrets because I can't figure how to avoid doing that when PKCE isn't allowed by the authorization server to get the tokens.

    So after thinking on all those problems, my second idea is to take advantage of my REST API and do the call to the authorization server from there. So my client would be confidential and I would do the OAuth flow with a Server-side Application.

    My idea is based on this image.

    enter image description here

    In order to avoid the client secret hardcoding in my mobile client, could the following code flow based on the image work and be safe to connect to Strava, Garmin, Polar....?

    Strava connection example:

    MOBILE CLIENT

    • Mobile public Client Calls my Rest API to get as a result the URI of Strava Authorization server login with needed params such as: callback, redirect_uri, client_it, etc.

    • Mobile client Catches the Rest API GET response URI.

    • Mobile client launches a user agent (Chrome custom tab) and listen to the callback.

    USER AGENT

    • The login prompt to strava is shown to the resource owner.

    • The resource owner inserts credentials and pushes authorize.

    • Callback is launched

    MOBILE CLIENT

    • When my client detects the callback, return to client and stract the code from the callback uri.

    • Send that code to my REST API with a post. (https://myrestapi with the code in the body)

    REST API CLIENT

    • Now, the client is my REST API, as it is going to be the one that calls the authorization server with the code obtained by the mobile client. The client will take that code and with the client secret hardcoded in it will call to the Authorization server. With this approach, the client secret is no more in the mobile client, so it is confidential.

    • The authorization server returns the tokens and I store them in a database.

    THE PROCESS

    • Takes those tokens from my database and make calls to the resource servers of strava to get the activities. Then parses those activities to my model and stores them into the database.

    Is this second approach a good way to handle the client secrets in order to avoid making them public? Or I am doing something wrong? Whatr flow could I follow to do it in the right way? I am really stuck with this case, and as I am new to OAuth world I am overwhelmed with all the information I have read.

    • Tasnuva Tavasum oshin
      Tasnuva Tavasum oshin over 2 years
      I think You can use IDENTITY SERVER for Identity Authorization take a look identityserver.com/articles/…
    • ch271828n
      ch271828n over 2 years
      There are 4 method in oauth2. Could you plz have a look at the standard "authorization-code" or "implicit' mode and see whether it fits your needs?
    • Jaime
      Jaime about 2 years
      @ch271828n I am using the authorization-code grant, but part of my client is in the mobile app and part in the rest api. The implicit code flow is not recommended right now as I have seen. My doubt is if my approach would be secure, as Stravas API only allows to use the client secret to obtain the tokens and I can't harcode it in my mobile app.
    • Jaime
      Jaime about 2 years
      @Tasnuvaoshin could you please explain your answer? What does Identity Server offers to my approach?
  • Jaime
    Jaime about 2 years
    I think that Strava's one has not public client option, so I have to use the client secret in order to obtain the tokens. As I cant hardcode the client secret in my mobile app Code, is a bad decision to pass the authorisation Code to my backend as a REST API call and obtain the tokens on the backend? Could you tell me how to obtain Strava's tokens if I al developing a mobile app?
  • Umakanth
    Umakanth about 2 years
    You could obtain the token on the backend, there is a pattern called BFF(backend for frontend) which you can use to handle the token's. Your mobile application does not have to deal with token's. Although this pattern is more inclined toward's SPA's rather than mobile applications, you could still give it a try
  • Jaime
    Jaime about 2 years
    So, obtaining the authorization code in the frontend and sending it to the backend in order to achieve the tokens in the backend would do the trick? In that way, the client secret would be placed in a secure place, I could store the tokens in a Database, do the calls to the APIs from the backend and map the responses in order to store the data in my datgabase tables. Am I right?
  • Umakanth
    Umakanth about 2 years
    Yes, although you may have to use in-memory database for token storage for efficiency and would have to think about how you are refreshing the tokens, managing their expiry. as they say "Devil is in the details :)"
  • Jaime
    Jaime about 2 years
    I will try to use my REST API as a confidencial client for Strava and Polar as they only work with the client secret and authorisation code Grant.Once I get the authorisation code, I will make a call to my rest API with that code and obtain the tokens storing them in my Database. Fitbit and Google on the other hand, support PKCE and don't need a client secret, so I can obtain the acccess and refresh tokens on the frontend and send them to the backend with a post call to my api in order to store them in my Database.My doubt is if for Google and Fitbit I should also get the tokens in the backend
  • Jaime
    Jaime about 2 years
    Instead of in the frontend in order to maintain an unified flow for all the oauths (Fitbit,Google,Polar,Strava...)Sending Googles and Fitbits authorisation code and code verifier to the backend in order to get the tokens from the backend would do the trick.I think that It is weird to get some of the tokens in the frontend(Google,Fitbit) and others in the backend(Strava,Polar).What do you think?The fact is that I need to have all my Users tokens in the backend in order to make backend calls to the resource servers of Strava, Polar, Google, Fitbit...and populating my database with the responses
  • Michal Trojanowski
    Michal Trojanowski about 2 years
    If you need those tokens in the backend anyway I would implement all providers in the same way as Strava and Polar. This way these tokens will never reach your mobile app, so an attacker will not be able to steal them.