3

I'm posting this on my way home, so forgive the lack of code but I'll try to be as detailed as possible and add code when I can tonight. So essentially I have a react native app using redux and axios. A brief review (code to follow) may explain that I'm doing something wrong.

Serviceapi.js Creates and exports basic axios with base url.

const ServiceApi = axios.create({
    baseURL: BASE_URL,
    responseType: 'json'
});

AuthReducer.js On login sets Authorization header manually using the post method. This works on both android and ios the login is returned and I use the authorization header.

return {
    type: PERFORM_LOGIN,
    payload: {
        user: {
            name: username
        },
        request: {
            url: '/login',
            method: 'post',
            headers: { 
                'Authorization': 'Basic ' + basicAuth
            }
        }
    }

On login, I return the following redux-axios action, you can see that I set the header: Authorization manually, this works great.

        // On login success, set the authInterceptor responsible for adding headers
        authInterceptor = ServiceApi.interceptors.request.use((config) => {
          console.log(`Attaching Authorization to header ${basicAuth}`);
          config.headers.common.Authorization = basicAuth;
          return config;
        }, (error) => {
          Promise.reject(error);
        });

On logout I clear the interceptor. I chose to add and remove on login and logout instead of always having it there just because. This could be a problem but it was fine for Android

// Clear the auth interceptor
ServiceApi.interceptors.request.eject(authInterceptor);

Again this is all working great on Android. And it looks to be working on ios. When I debug the interceptor it's getting called and setting the header.

But I get back a 403 on ios. After looking at the request in more detail, there is a big difference between the android header in the request and the ios header in the request. The rest of the request object is the same, only the _header object is different between ios and android.

Android Request

    _headers:
        accept: "application/json, text/plain, */*"

        authorization: "Basic <correct base64 value>"

        content-type: "application/json;charset=utf-8"
__proto__: Object


IOS Request

    _headers:
        accept: (...)

        authorization: (...)

        content-type: (...)

        get accept: ƒ ()
        
set accept: ƒ ()

        get authorization: ƒ ()
        
set authorization: ƒ ()
        
get content-type: ƒ ()

        set content-type: ƒ ()

        __proto__: Object


With the differences, setting a breakpoint at looking at the console for error.request._headers.authorization; I get the same "Basic: " contents as the Android header contains.

index.php The backend service is a php file that does a $_SERVER['PHP_AUTH_USER'] which fails a 403 if not set which is what's happening. I don't have access to the php, I was just told this is what it's using.

Again I apologize for not providing code but i will when I get a chance later. Is there something maybe I have to set extra for ios? Or maybe php for ios needs an extra header?

Code to follow.

EDIT Updated with code, hopefully I didn't leave in any of the encoded login info.

EDIT 2 Upon further investigation this looks like it's related to apache/PHP rather than react-native/axios. I threw together an express server that simulated the same checking that the PHP does: - Look for the Authorization header - Print it - Return back 403 or 200 w/ data based on that

When running pointing at http://localhost:3000 using the exact same app on the emulator I get back what I'm expecting. To add to this, when I'm on the emulator, I can't actually login to the live URL (even though I could on the regular device), I get the same 403 error but this time a little earlier.

EDIT 3

To provide some more information from the server, here are the three requests that I've been able to log:

1) This is from the IOS Emulator iPhone8 against a an express server:

accept:"application/json, text/plain, */*"
accept-encoding:"gzip, deflate"
accept-language:"en-us"
authorization:"Basic <base 64 encoding>"
connection:"keep-alive"
content-length:"0"
host:"localhost:3000"
user-agent:"MobileApp/1 CFNetwork/978.0.7 Darwin/18.5.

2) This is from the same emulator to apache/PHP (5.3.3), we can see there is no Authorization header.

Accept: application/json, text/plain, */*                                                             
User-Agent: MobileApp/1 CFNetwork/978.0.7 Darwin/18.5.0       
Accept-Language: en-us                                                    
Accept-Encoding: br, gzip, deflate                                        
Connection: keep-alive     

3) This is from Android to apache/PHP (5.3.3):

authorization: Basic <Base 64 encoding> 
Host: api.serviceurl.com
Connection: Keep-Alive                            
Accept-Encoding: gzip                                 
User-Agent: okhttp/3.12.1

Edit 4 So after playing around and googling for some time, it turns out that the issue is with Zend Framework and fastcgi which automatically removes the Authorization header. The weird thing is that it's only doing it from IOS and not from Android, which makes no sense really.

On thing we noticed in the logs, is that it's accepting the Android and Postman as POST but it's logging the IOS requests as GET. I'm not entirely sure what's up with that, but it seems to be another difference. I've updated the task to have zend as a tag. There are a number of SO articles on resolving this with ReWriteMod on apache/zend so I'll give those a go first and see if it fixes the issue.

** Edit 5** So far we've attempted to follow the SO articles which ask that that that following be added (Authorization header missing in django rest_framework, is apache to blame?):

SetEnvIfNoCase Authorization ^(.*) -e=PHP_HTTP_AUTH

RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

which results in the following:

// IOS
_SERVER[PHP_HTTP_AUTH] = <blank>
_SERVER[HTTP_AUTHORIZATION] = <blank>

// Android
_SERVER[PHP_HTTP_AUTH] = Username
_SERVER[HTTP_AUTHORIZATION] = Basic <Base65 encoded>
_SERVER[PHP_HTTP_PW] = Password

So we know that Header Authorization is getting to Apache, but now it's coming through as blank. There are a few other SO answers I'm researching but the search continues...

Edit 6

Resolved(ish)

kendavidson
  • 1,245
  • 1
  • 8
  • 16
  • Is the URL you're connecting to behind http or https? I recall there being a setting in `info.plist` that specifies the URLs you can connect to via non-secured calls. Could it be something along those lines? – Tim Lewis Jun 24 '19 at 21:00
  • Https. The login uses the same axios client. The only difference is that I do a. Post(/login, {headers:{}}) manually. While for all other requests I use the interceptor. I could rewrite the client to set the user pass manually all the time but I figured using an interceptor was better practice. I apologize I'm also on my phone for another couple hours. So forgive the typos. – kendavidson Jun 24 '19 at 21:20
  • Ah I see. Can't say I'm familiar with the concept of an interceptor in `react-native`. Is that a part of `redux`? Sounding more and more like this will be easier to debug when code is posted though :) – Tim Lewis Jun 24 '19 at 21:24
  • 1
    The interceptor is an axios thing. Using something similar to https://gist.github.com/srph/38f67a10e991b6cb2d83 this. But I set the interceptor in the LOGIN_SUCCESS reducer action instead of keeping it in the asios client file. I appreciate the look. Will post code in t-minus bedtime. – kendavidson Jun 24 '19 at 21:36

1 Answers1

3

Turns out it was a trailing slash required on the request for IOS. I was able to find this link https://github.com/square/retrofit/issues/1037 where the the issue was described as:

For those interested: We are using Django as our backend and by default when you do not provide a trailing slash on the endpoint Django redirects from the non-slash endpoint to the slash endpoint.

Now, we aren't using Django, but apparently for our configuration of Zend it was the same issue - Android was able to re-direct without issue, while IOS was not. Another comment on the task states:

OkHttp strips the "Authorization" header when redirected across hosts (connections) via a 3xx response from the original host.

Which doesn't seem accurate, since Android was using OkHttp and was working fine. It looked like IOS using Darwin had the issue.

EDIT I forgot something else from my original post, I also had to change my interceptor from the line config.headers.common.Authorization = ... to config.headers.Authorization = ... which for some reason kept the casing. Original way converted Authorization to authorization, while the latter kept it as Authorization. Not sure if this was an issue, but I made it anyhow.

// On login success, set the authInterceptor responsible for adding headers
authInterceptor = ServiceApi.interceptors.request.use((config) => {
      console.log(`Attaching Authorization to header ${basicAuth}`);
      config.headers.Authorization = basicAuth;
      return config;
    }, (error) => {
      Promise.reject(error);
    });
kendavidson
  • 1,245
  • 1
  • 8
  • 16