HowTo: JSON Web Token from iOS and Android with React Native

JSON Web Token (JWT) is a standard for transmitting information securely. JWT is used to display, upload and delete information from an API server.

There are two parts to using JSON Web Token:

  1. backend server to create and then verify token;
  2. frontend application (in this case, an iOS app) to first request the web token and then use the web token in subsequent requests to access the resource.

For example, to run a REST command such as display image or delete image using JWT, your iPhone application must first request a JWT token from the backend server, then send the token it receives in the header of the HTTP request.

Although this may sound complex, it is simple to use the token. Normally, when you send a request for an image, you’re focused on the URL. For example, this is a standard URL https://theta-image-api.s3.amazonaws.com/thumbnails/theta_images/car_med_800.jpeg

To apply the token, you use the Authorization syntax below.

headers: {
    'Authorization': `Bearer ${accessToken}`,
},

More information JWT is available here:

Setting up mobile app for JWT

https://github.com/codetricity/jwt-react-native-tutorial

Thanks to @Phat_Ca for building this demo.

The following two examples use the common fetch syntax of JavaScript.

fetch token

    try {
      const response = await fetch('https://image360.oppget.com/api/token/', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          username,
          password,
        }),
      });

use token in the REST request

const response = await fetch('https://image360.oppget.com/api/user-photo/', {
method: 'GET',
headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
},
});

You can use the GitHub code as an example on integrating fetch with your React Native app.

Setting Up Django for Secure 360 Image Management with JWT

To serve a REST API with JWT authentication using Django, you need the packages djangorestframework and djangorestframework-simplejwt.

The packages will handle token generation, security, and REST management.

Here’s a step-by-step guide on how to set it up:

  1. Set up Django and Django REST Framework (DRF)
    First, ensure you have a Django project set up with Django REST Framework (DRF) installed:
pip install django djangorestframework

Add ‘rest_framework’ to your INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    # Other apps...
    'rest_framework',
]
  1. Install Django REST Framework Simple JWT
    For JWT-based authentication, install the djangorestframework-simplejwt package:
pip install djangorestframework-simplejwt

Then, add rest_framework_simplejwt to your INSTALLED_APPS:

INSTALLED_APPS = [
    # Other apps...
    'rest_framework_simplejwt',
]
  1. Configure JWT Authentication in settings.py
    Update the REST_FRAMEWORK settings to use JWT for authentication:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}
  1. Create JWT Views for Token Handling
    In your urls.py file, set up paths for obtaining and refreshing tokens:
from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

TokenObtainPairView: Returns an access token and refresh token.
TokenRefreshView: Refreshes the access token using the refresh token.

  1. Creating a Simple API View
    Now, you can create a simple API view that requires authentication:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class ProtectedAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({"message": "You are authenticated!"})

Then, add this view to your urls.py:

urlpatterns = [
    path('api/protected/', ProtectedAPIView.as_view(), name='protected'),
    # JWT token paths
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
  1. Using the API
    To authenticate, you’ll first obtain a JWT token by sending a POST request to /api/token/ with valid user credentials (e.g., username and password). The response will include an access token (for authorization) and a refresh token (for getting a new access token when it expires).
POST /api/token/
{
  "username": "your_username",
  "password": "your_password"
}

You’ll get a response like:

{
  "refresh": "your_refresh_token",
  "access": "your_access_token"
}

Then, include the access token in the Authorization header when making requests to protected endpoints:

Authorization: Bearer your_access_token
  1. Token Expiration and Refreshing
    When the access token expires, you can refresh it using the refresh token by sending a POST request to /api/token/refresh/:
POST /api/token/refresh/
{
  "refresh": "your_refresh_token"
}

You’ll receive a new access token in the response.

  1. Optional: Customizing Token Payload or Expiration
    You can also customize JWT behavior in settings.py. For example, to modify the token expiration time:
from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    # other settings...
}

Try Out Live Servers

Oppkey API Developer Demonstration Server

You can access the APIs for the Oppkey Django server here:

https://image360.oppget.com/api/

API Authentication Information

username: tutorial
password: theta360DotGuide

Do not upload private images. This is a public playground for testing.

Run React Native Mobile App with Existing Live image360 Demo Server

Clone down the repo for the demonstration mobile app, install the JavaScript dependencies and run the app on Android or iOS.

git clone https://github.com/codetricity/jwt-react-native-tutorial.git
npm install
npm start

For Android

# using npm
npm run android

For iOS

# using npm
npm run ios

Login

image

username: tutorial (make sure T is lowercase) if you have problems with auto-capitalization, then type Ttutorial and delete the first T.
password: theta360DotGuide

Image Display

After the token is generated, this endpoint gets all the pictures. Since the web token is used, user can be obtained.

This is the mobile app code.

const response = await fetch('https://image360.oppget.com/api/user-photo/', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
});

server code

def get(self, request):
    try:
        photos = Photo.objects.filter(user=request.user).all()
        serializer = PhotoSerializer(photos, many=True)
        return Response(serializer.data)

Deletion, frontend mobile app code

const response = await fetch(`https://image360.oppget.com/api/${id}/delete/`, {
  method: 'DELETE',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
});

Troubleshooting React Native Mobile App

Unable to load contents of file list

pod deintegrate
pod install

Next Steps

You now have a Django application serving a REST API with JWT authentication. You can create additional views and restrict access using the JWT token. For more advanced use cases, you can further customize the JWT payload, token lifetime, and authentication logic.

In the next article, we will show how to upload a 360 image to the API server from the iOS cameraroll.

We can get the images from the RICOH THETA camera to the iOS camera using this solution.

From here, we need to use a file picker to pick the file from the camera roll and upload it to the cloud API server with JWT.

Adding Features

At this stage of the application, users are able to use their credentials to log in to the app and receive a JWT token, which is stored in LocalStorage. After that, users are directed to the Home screen, where the app fetches all images belonging to them using their JWT credentials. On the Home screen, besides viewing the image listing, users also have the option to delete selected images. Additionally, there is an option for users to upload images to the database for testing using the “Upload Image” button.

User Authentication

Code development and description of workflow here is contributed by @Phat_Ca , computer science major, University of Hawaii at Manoa.

Note from @Phat_Ca :

The majority of the implementation and features are currently in App.tsx, which is not best practice for React Native as it’s recommended to break down functionality into separate components. I plan to refactor each feature into its own component for better readability and adherence to best practices. This restructuring will enhance the code organization and maintainability.

User Authentication

To implement secure user access, we added a basic token-based authentication system to the app. Our goal was to allow users to log in with credentials that would be verified by a backend server, with successful authentication granting access to the main app features. Below are the steps we used to implement this feature:

  1. Code Snippet for User Login:

App.tsx:

const LoginScreen = ({ navigation }) => {
  const [username, setUserName] = useState('');
  const [password, setPassword] = useState('');
  const [errorMessage, setErrorMessage] = useState('');

  const handleLogin = async () => {
    try {
      const response = await fetch('https://image360.oppget.com/api/token/', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          username,
          password,
        }),
      });

      if (response.ok) {
        const data = await response.json();
        if (data.access) {
          await AsyncStorage.setItem('accessToken', data.access);
          await AsyncStorage.setItem('refreshToken', data.refresh);
          console.log('Access token saved successfully:', data.access);

          // Navigate to the Home screen
          navigation.navigate('Home');
        } else {
          setErrorMessage('No access token found');
        }
      } else {
        const data = await response.json();
        setErrorMessage(data.message);
      }
    } catch (error) {
      setErrorMessage('An error occurred: ' + error);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Username"
        value={username}
        onChangeText={setUserName}
      />
      <TextInput
        style={styles.input}
        placeholder="Password"
        secureTextEntry
        value={password}
        onChangeText={setPassword}
      />
      {errorMessage ? <Text style={styles.error}>{errorMessage}</Text> : null}
      <Button title="Login" onPress={handleLogin} />
    </SafeAreaView>
  );
};
  1. Login Screen Setup:
  • The app provides a LoginScreen component for user login, managed with React’s useState hooks to handle the username, password, and errorMessage states. These states help manage the form inputs and display error messages if login attempts fail.
  1. Login Process:
  • The handleLogin function within LoginScreen is the core of the login process. This function executes when the user taps the “Login” button, attempting to authenticate with the backend server.

  • Within handleLogin, the app sends a POST request to an API endpoint (https://image360.oppget.com/api/token/) with username and password in the request body.

  • The request is crafted using the fetch API, and headers include Content-Type: application/json to specify JSON format for the data.

  1. Response Handling:
  • If the server responds with a success status, the app checks if an access token is included in the response.

  • If an access token is received, the user is redirected to the Home screen via navigation.navigate(‘Home’). This navigation happens right after the token check, creating a seamless transition from login to the authenticated home view.

Adding Features

JWT Token Storage

Code development and description of workflow here is contributed by @Phat_Ca , computer science major, University of Hawaii at Manoa.

Note from @Phat_Ca :

The majority of the implementation and features are currently in App.tsx, which is not best practice for React Native as it’s recommended to break down functionality into separate components. I plan to refactor each feature into its own component for better readability and adherence to best practices. This restructuring will enhance the code organization and maintainability.

JWT Token Storage

To securely manage user sessions, we implemented JWT token storage within the app. This setup allows the app to persist user authentication between sessions by storing tokens on the device, providing a seamless experience while maintaining security. Below are the steps we used to implement this feature:

  1. Code Snippet for Token Storage:

App.tsx:

if (data.access) {
  await AsyncStorage.setItem('accessToken', data.access);
  await AsyncStorage.setItem('refreshToken', data.refresh);
  console.log('Access token saved successfully:', data.access);

  // Navigate to the Home screen
  navigation.navigate('Home');
} else {
  setErrorMessage('No access token found');
}
  1. Access and Refresh Token Management:
  • Upon receiving tokens, the app stores both accessToken and refreshToken in AsyncStorage.
  • Storing tokens locally allows the app to access them quickly when making subsequent requests, avoiding unnecessary reauthentication.
  1. Security Consideration:
  • By using AsyncStorage for token storage, we ensure that sensitive information is retained securely and can be accessed when the app needs to verify user sessions.
  • In addition by storing tokens rather than sensitive credentials (like the username and password) in AsyncStorage, the app reduces security risks. In the event of a malicious attack or unauthorized access, users’ actual credentials remain safe, as only the tokens are stored.

Adding Features

Image Fetching

Code development and description of workflow here is contributed by @Phat_Ca , computer science major, University of Hawaii at Manoa.

Note from @Phat_Ca :

The majority of the implementation and features are currently in App.tsx, which is not best practice for React Native as it’s recommended to break down functionality into separate components. I plan to refactor each feature into its own component for better readability and adherence to best practices. This restructuring will enhance the code organization and maintainability.

Image Fetching

To enhance the user experience, we implemented an image-fetching feature on the home screen that retrieves and displays user-specific images upon login. This feature relies on authenticated API requests to fetch content securely. Below are the steps we used to implement this feature:

  1. Code Snippet for Image Fetching:

App.tsx:

const HomeScreen = ({ navigation }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    try {
      const accessToken = await AsyncStorage.getItem('accessToken');
      if (!accessToken) {
        throw new Error('No access token found');
      }
      const response = await fetch('https://image360.oppget.com/api/user-photo/', {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      });
      if (!response.ok) {
        throw new Error('Failed to fetch images');
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);
}
  1. Authenticated API Request:
  • The fetchData function retrieves an access token from AsyncStorage and uses it in the Authorization header to securely request images from the backend. This ensures that only authenticated users can access this content.
  1. Error Handling and Loading State:
  • Error handling is implemented to catch and display any issues during the fetch request. Additionally, a loading state is set while the request is in progress, improving the user experience by providing feedback.
  1. Security Considerations:
  • By using the accessToken from secure storage and ensuring that images are fetched only for authenticated users, this feature maintains security while offering personalized content.

Adding Features

Image Deletion

Code development and description of workflow here is contributed by @Phat_Ca , computer science major, University of Hawaii at Manoa.

Note from @Phat_Ca :

The majority of the implementation and features are currently in App.tsx, which is not best practice for React Native as it’s recommended to break down functionality into separate components. I plan to refactor each feature into its own component for better readability and adherence to best practices. This restructuring will enhance the code organization and maintainability.

Image Deletion

To allow users greater control over their content, we implemented an image deletion feature on the home screen. This feature enables users to remove unwanted images securely and ensures that deletion requests are handled through authenticated API calls. Below are the steps we used to implement this feature:

  1. Code Snippet for Image Deletion:

App.tsx:

const deleteImage = async (imageId) => {
  try {
    const accessToken = await AsyncStorage.getItem('accessToken');
    if (!accessToken) {
      throw new Error('No access token found');
    }
    const response = await fetch(`https://image360.oppget.com/api/user-photo/${imageId}/`, {
      method: 'DELETE',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
    });
    if (!response.ok) {
      throw new Error('Failed to delete image');
    }
    console.log('Image deleted successfully');
  } catch (error) {
    console.error(error.message);
  }
};
  1. Authenticated Deletion Request:
  • The deleteImage function retrieves the accessToken from AsyncStorage and includes it in the Authorization header, enabling only authenticated users to delete images. This secure request removes the specified image from the backend.
  1. Error Handling:
  • The function includes error handling to catch any issues during the deletion process, displaying an error message if the request fails or if authentication is not available (where token expired).
  1. User Interface Update:
  • After a successful deletion, the app logs a confirmation and can update the UI to reflect the change, such as removing the deleted image from view or refreshing the image list.

Adding Features

Image Upload

Code development and description of workflow here is contributed by @Phat_Ca , computer science major, University of Hawaii at Manoa.

Note from @Phat_Ca :

The majority of the implementation and features are currently in App.tsx, which is not best practice for React Native as it’s recommended to break down functionality into separate components. I plan to refactor each feature into its own component for better readability and adherence to best practices. This restructuring will enhance the code organization and maintainability.

Image Upload

The image upload form allows users to select, customize, and upload images with metadata like title, description, and date taken. This feature includes image selection, compression, and authenticated uploads to the server. During implementation, I encountered challenges with image sizing and date compatibility, which are explained in detail below.

src/UploadForm.js

const uploadImage = async () => {
  if (!imageUri) {
    Alert.alert('Please select an image first');
    return;
  }
  if (!title) {
    Alert.alert('Please provide a title.');
    return;
  }

  try {
    const compressedImageUri = await compressImage(imageUri);

    const formData = new FormData();
    const modifiedImageName = imageName.endsWith('.jpg') ? imageName : `${imageName}.jpg`;

    formData.append('image', {
      uri: compressedImageUri,
      name: modifiedImageName,
      type: 'image/jpeg',
    });
    formData.append('title', title);
    formData.append('description', description);
    formData.append('attribution', attribution);
    formData.append('license_link', licenseLink);
    formData.append('date_taken', dateTaken.toISOString().split('T')[0]);
    formData.append('camera_model', cameraModel);
    formData.append('category', category);
    formData.append('order', order);
    const token = await AsyncStorage.getItem('accessToken');
    setUploading(true);

    const response = await axios.post(
      'https://image360.oppget.com/api/upload-photo/',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: `Bearer ${token}`,
        },
      }
    );

    Alert.alert('Image uploaded successfully!');
    console.log('Server response:', response.data);
  } catch (error) {
    console.log('Error Response:', error.response);
    Alert.alert('Upload failed: ' + error.message);
  } finally {
    setUploading(false);
  }
};
  1. Image Selection and Compression:
  • The pickImage function lets users select an image from their gallery, and compressImage resizes it to optimize upload performance.

Difficulty that I found: When I first worked with this feature, I did not use compressImage to resize the image. As a result, for some images, I encountered an error indicating they were too large, and the server timed out and rejected the request when I attempted to upload them.

  1. Form Fields for MetaData:
  • Additional form fields enable users to enter metadata such as title, description, camera model, license, and category. Each field is stored in state and appended to formData for the upload request.

Compatibility Challenge: Because we are using a Django setup for the backend with a DateField for the date, I initially had to research compatibility. I found that DateTimePicker is a useful package for React Native, providing a well-designed UI component for iOS and formatting the date correctly to match our backend implementation.

  1. Authenticated Upload Request:
  • The uploadImage function retrieves the accessToken from AsyncStorage and includes it in the request headers to ensure only authenticated users can upload images. The function then sends the image and metadata to the server.
  1. Error Handling:
  • If any required fields are missing, alerts prompt users to complete them. The app also includes error handling for upload failures, displaying an alert with a descriptive message.

@jcasman are you writing an article on image upload with camera roll selection? Where is the pickImage function you mentioned? Does it require a new react native package?

It was challenging to save the images to the iOS camera roll. I am wondering about the access to pick and upload.

Did you update the repo? The last article did not have the image upload functionality.

@craig Hi Craig,

The pickImage function utilizes the Image Picker package from react-native-image-picker. This great native module allows the user to select a photo/video from the device library or camera. However, to use this, you will need to request permission in the Info.plist file (IOS) for access permission

<key>NSPhotoLibraryUsageDescription</key>
<string>We need your permission to access the photo library</string>

The code for pickImage function

const pickImage = () => {
    launchImageLibrary({ mediaType: 'photo' }, (response) => {
      if (response.didCancel) {
        Alert.alert('Image selection canceled');
      } else if (response.errorMessage) {
        Alert.alert('Error: ' + response.errorMessage);
      } else {
        const uri = response.assets[0].uri;
        setImageUri(uri);
      }
    });
  };

The function allows users to pick a photo from their device’s library and includes basic error handling for both user cancellations and image selection errors. The image’s URI is used to reference the selected image for further processing or display.

Additionally, here is my GitHub repository for the application.

2 Likes

@Phat_Ca , thanks for this information on react-native-image-picker. I’m still amazed whenever the app can store and read from the camera roll on an iPhone. :slight_smile:

It would be great if you can extend your mobile app with these issues.

If you are stuck with using Marzipano in a WebView, you can work on the “account registration screen” first, then try to get help from @shegz101 on showing Marzipano in the WebView.

He has Pannellum working in a WebView on React Native.

testing

npm install
npm start

In separate windows

npm run android

In this case, I downloaded an image from our site onto the local downloads section on the AVD.

I have a problem with this calendar selector appearing all the time.


Not seeing this problem on iOS, only on Android.