Debug School

rakesh kumar
rakesh kumar

Posted on • Edited on

How to Secure Laravel API Routes From Public Access

How to implement Bearer Token
what is x-play-integrity
Difference and Role of the Two Tokens
Flutter - Secure API base URL & Other URL's
How to Enable Play Integrity API in playstore
Flutter Frontend (Full Example)
a) Login and Store Token

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

// Login, receive token, and store in SharedPreferences
Future<void> loginUser(String email, String password) async {
  final url = 'https://yourdomain.com/api/login';
  final response = await http.post(
    Uri.parse(url),
    headers: {"Content-Type": "application/json"},
    body: jsonEncode({
      "email": email,
      "password": password,
    }),
  );

  if (response.statusCode == 200) {
    final decoded = jsonDecode(response.body);
    final token = decoded['token']; // <-- Adjust key if your response is different
    // Store token securely
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('accessToken', token);
    print('Login successful, token stored');
  } else {
    throw Exception('Login failed: ${response.body}');


}
}
Enter fullscreen mode Exit fullscreen mode

pacticalway

   String token = responseData['token'].toString(); // Extract email
        // Save email securely
        await _saveEmailCredentials(email,token);
Enter fullscreen mode Exit fullscreen mode


Future<bool> verify_otp(String phone, String otp, BuildContext context) async {
  final prefs = await SharedPreferences.getInstance();
  String? dialCode = prefs.getString("dial_code");

  if (dialCode == null || dialCode.isEmpty) {
    print("Dial code is missing in SharedPreferences");
    return false;  // Handle case where dial code is not set
  }

  // Concatenate dial code with phone (without + sign)
  String combinedPhone = "$dialCode$phone";

  var url = "$baseUrl/api/verify_otp";
  var body = jsonEncode({"phone": combinedPhone, "otp": otp});
  print("Request URL: $url");
  print("Request Body: $body");

  try {
    final response = await http.post(
      Uri.parse(url),
      headers: {"Content-Type": "application/json"},
      body: body,
    );

    print("Response verify_otp Status Code: ${response.statusCode}");
    print("Response Body verify_otp: ${response.body}");

    if (response.statusCode == 200) {
      var responseData = jsonDecode(response.body);

      // Check if the response indicates success
      if (responseData['success'] == true) {
        String email = responseData['user']['email'].toString(); // Extract email
        print("Email received: $email");
   String token = responseData['token'].toString(); // Extract email
        // Save email securely
        await _saveEmailCredentials(email,token);
        print("Phone and OTP saved securely!");
        return true;  // Indicate success
      } else {
        print("Invalid response format or OTP missing");
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => Register(phone: combinedPhone),
          ),
        );
        return false;
      }
    } else {
      print("Failed request with status: ${response.statusCode}");
      return false;
    }
  } catch (e) {
    print("Exception: $e");
    throw Exception("An error occurred while processing the login request");
  }
}
Enter fullscreen mode Exit fullscreen mode

Use Stored Token For Authenticated API Requests

Future<String?> getToken() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString('accessToken');
}

Future<List<AddVehicle>> getVehicleData() async {
  final url = 'https://yourdomain.com/api/getmyvehicle';
  final token = await getToken();

  final response = await http.post(
    Uri.parse(url),
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer $token", // Secure header
    },
    body: jsonEncode({}),
  );
  print("Response Status: ${response.statusCode}");
  if (response.statusCode == 200) {
    List<dynamic> data = jsonDecode(response.body)['data'];
    return data.map((json) => AddVehicle.fromJson(json)).toList();
  } else {
    throw Exception('Failed: ${response.body}');
  }
}
Enter fullscreen mode Exit fullscreen mode

practical way

  1. Laravel Backend
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\PartnerController;

// Public route(s)
Route::post('login', [AuthController::class, 'login']);

// Protected routes
Route::middleware('auth:sanctum')->group(function () {
    Route::post('getmyvehicle', [PartnerController::class, 'getmyvehicle']);
    // Add all other protected routes here...
});
Enter fullscreen mode Exit fullscreen mode

practical way

AuthController: Issue a Token

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User; // Adjust namespace/Model as needed
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $user = User::where('email', $request->email)->first();
        if ($user && Hash::check($request->password, $user->password)) {
            $token = $user->createToken('mobile')->plainTextToken;
            return response()->json([
                'token' => $token,
                'user' => $user,
            ]);
        } else {
            return response()->json(['error' => 'Invalid credentials'], 401);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

practical way

Example PartnerController with Auth

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class PartnerController extends Controller
{
    public function getmyvehicle(Request $request)
    {
        $user = $request->user();
        // Example: send all vehicles owned by user
        $vehicles = $user->vehicles; // Adjust relation/model as needed
        return response()->json([
            'data' => $vehicles,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

======================================================
C:\Project\motoshare_partner\android\app\src\main\kotlin\com\example\motoshare_partner\MainActivity.kt

package com.cotocus.motoshare.partner

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.IntegrityTokenRequest

class MainActivity : FlutterActivity() {
    private val CHANNEL = "play_integrity_channel"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            if (call.method == "getPlayIntegrityToken") {
                val nonce = call.argument<String>("nonce") ?: ""
                getPlayIntegrityToken(nonce, result)
            }
        }
    }

    private fun getPlayIntegrityToken(nonce: String, result: MethodChannel.Result) {
        val integrityManager = IntegrityManagerFactory.create(this)
        val request = IntegrityTokenRequest.builder().setNonce(nonce).build()
        integrityManager.requestIntegrityToken(request)
            .addOnSuccessListener { response ->
                result.success(response.token())
            }
            .addOnFailureListener { e ->
                result.error("INTEGRITY_ERROR", e.localizedMessage, null)
            }
    }
}
Enter fullscreen mode Exit fullscreen mode

step2

C:\Project\motoshare_partner\android\app\build.gradle

     implementation 'com.google.android.play:integrity:1.3.0'
Enter fullscreen mode Exit fullscreen mode

step3

C:\Project\motoshare_partner\lib\Service\Influencer\motoshare_service.dart

import 'dart:convert';
import 'dart:math';
import 'package:flutter/services.dart';

 const platform = MethodChannel('play_integrity_channel');

Future<String?> getPlayIntegrityToken(String nonce) async {
  try {
    return await platform.invokeMethod<String>('getPlayIntegrityToken', {'nonce': nonce});
  } on PlatformException catch (e) {
    print("Failed to get Play Integrity token: $e");
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode
String generateWebSafeNonce([int length = 32]) {
  final random = Random.secure();
  final bytes = List<int>.generate(length, (_) => random.nextInt(256));
  return base64UrlEncode(bytes); // Web-safe, no-wrap!
}
Enter fullscreen mode Exit fullscreen mode
Future<List<AddVehicle>> getVehicleData(String email) async {
  var url = "$baseUrl/api/getVehicleData";
  var body = jsonEncode({"email": email});
final token = await getToken();
  print("Request URL: $url");
  print("Request Body: $body");
final nonce = generateWebSafeNonce(); // 32 bytes
final integrityToken = await getPlayIntegrityToken(nonce);
            print("intigrity token is");
                 print(integrityToken);
  final response = await http.post(
    Uri.parse(url),
    headers: {"Content-Type": "application/json",
     "Authorization": "Bearer $token",
    },
    body: body,
  );

  print("Response Status: ${response.statusCode}");
  print("Response Body getVehicleData: ${response.body}");

  if (response.statusCode == 200) {
    List<dynamic> body = jsonDecode(response.body)['data']; // Access 'data' key
    return body.map((vehicleData) => AddVehicle.fromJson(vehicleData)).toList();


  } else {
    throw Exception("Failed to fetch vehicle data, status: ${response.statusCode}");
  }
}
Enter fullscreen mode Exit fullscreen mode

restart emulator

step4:
Send Integrity Token With Your API Request
Modify your API call to include the integrity token

Future<List<AddVehicle>> getVehicleData(String integrityToken, String authToken) async {
  var url = "$baseUrl/api/getmyvehicle";

  final response = await http.post(
    Uri.parse(url),
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer $authToken",                   // your login/session token (as before)
      "X-PLAY-INTEGRITY": integrityToken,                     // NEW: add Play Integrity token
    },
    body: jsonEncode({}),
  );

  print("Response Status: ${response.statusCode}");
  print("Response Body: ${response.body}");

  if (response.statusCode == 200) {
    List<dynamic> body = jsonDecode(response.body)['data'];
    return body.map((vehicleData) => AddVehicle.fromJson(vehicleData)).toList();
  } else {
    throw Exception("Failed to fetch vehicle data, status: ${response.statusCode}");
  }
Enter fullscreen mode Exit fullscreen mode

Difference and Role of the Two Tokens

Is there a way a random public internet user can access your API?

Restrict Routes in routes/api.php

Route::middleware(['auth:sanctum', 'play.integrity'])->group(function() {
    Route::post('getprofile', [PartnerController::class, 'getprofile']);
    // ...all protected routes
});
Enter fullscreen mode Exit fullscreen mode

=================or==================

Route::middleware(['play.integrity'])->group(function () {
    Route::post('guest-only-action', [PartnerController::class, 'guestOnlyAction']);
    // Add other "public but real app only" routes here
});
Enter fullscreen mode Exit fullscreen mode

Middleware: Play Integrity Only

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class PlayIntegrityMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $token = $request->header('X-PLAY-INTEGRITY');
        if (!$token) {
            return response()->json(['error' => 'Missing Integrity Token'], 401);
        }

        $apiKey = config('services.play_integrity.api_key');
        $packageName = config('services.play_integrity.package_name');

        // Replace with Google Play Integrity API call for production
        $response = Http::withHeaders([
            'Authorization' => "Bearer $apiKey",
        ])->post(
            "https://playintegrity.googleapis.com/v1/$packageName:decodeIntegrityToken",
            [
                'integrity_token' => $token
            ]
        );

        if ($response->failed() || !isset($response['tokenPayloadExternal'])) {
            return response()->json(['error' => 'Integrity check failed'], 401);
        }

        $payload = $response['tokenPayloadExternal'];
        // Only allow requests from your real app on Play Store
        if (
            $payload['appIntegrity']['appRecognitionVerdict'] !== 'PLAY_RECOGNIZED' ||
            $payload['appIntegrity']['packageName'] !== $packageName
        ) {
            return response()->json(['error' => 'Untrusted app environment'], 401);
        }

        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

=====================or====================

// app/Http/Middleware/CheckPlayIntegrity.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class CheckPlayIntegrity
{
    public function handle(Request $request, Closure $next)
    {
        $token = $request->header('X-PLAY-INTEGRITY');
        if (!$token) {
            return response()->json(['error' => 'No Integrity Token'], 401);
        }

        // Verify the token with Google Play backend
        $apiKey = config('services.play_integrity.api_key');
        $packageName = config('services.play_integrity.package_name');

        $response = Http::withHeaders([
            'Authorization' => "Bearer $apiKey",
        ])->post(
            "https://playintegrity.googleapis.com/v1/$packageName:decodeIntegrityToken",
            [
                'integrity_token' => $token
            ]
        );

        if ($response->failed() || !isset($response['tokenPayloadExternal'])) {
            return response()->json(['error' => 'Integrity check failed'], 401);
        }

        $payload = $response['tokenPayloadExternal'];

        // Example: Ensure app is installed from Google Play and is unmodified
        if (
            $payload['appIntegrity']['appRecognitionVerdict'] !== 'PLAY_RECOGNIZED' ||
            $payload['appIntegrity']['packageName'] !== $packageName
        ) {
            return response()->json(['error' => 'Untrusted app environment'], 401);
        }

        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode
'play.integrity' => \App\Http\Middleware\CheckPlayIntegrity::class,
Enter fullscreen mode Exit fullscreen mode

How to Enable Play Integrity API in playstore

when play integrity is not setup it is written
integrity not started

By enabling Play Integrity API responses in the Google Play Console, you will gain access to additional configuration options, testing features, and API reporting. This option is only available to apps distributed on Google Play. Navigate to Release > App integrity. Under Play Integrity API select Link a Cloud project. Choose the Cloud project you want to link to your app and this will enable Play Integrity API responses. You can now integrate the Play Integrity API into your app.

Step1 click link cloud project

step2:click link project

now it is written integration started

enable play-integrity-setup

Flutter - Secure API base URL & Other URL's

https://www.youtube.com/watch?v=aewc3rYocWY
Enter fullscreen mode Exit fullscreen mode

Step-by-Step: How to Get $apiKey (Play Integrity Server API Key)
Create a Service Account in Google Cloud Platform
Go to Google Cloud Console

Select or create the project that matches your Android app in Google Play Console.

In the left menu, go to IAM & admin > Service accounts

Click Create Service Account

Provide a name, then Create and continue

Grant the service account the role "Play Integrity API User"

Download the Service Account JSON Key
After creating the account, click on it in the list.

Go to the Keys tab.


Choose Add Key > Create new key > JSON

Download the JSON key file (keep this file secure!).
Enter fullscreen mode Exit fullscreen mode

Place this file somewhere secure on your server, such as storage/app/play-integrity-service-account.json or follow Google’s recommended paths ([reference]).

Grant API Access to Play Integrity API
In your Google Cloud project, enable the Play Integrity API (search for it in the APIs & Services Library).

Make sure your app in the Play Console is linked to this cloud project ([reference]).

Set Environment Variable or Laravel Config
You do not copy a single string token into your .env, instead, you reference the downloaded JSON key file for authentication.

In your .env:

GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/play-integrity-service-account.json
PLAY_INTEGRITY_PACKAGE_NAME=com.yourcompany.yourapp
Enter fullscreen mode Exit fullscreen mode

In config/services.php (example):

'play_integrity' => [
    'json_credentials' => env('GOOGLE_APPLICATION_CREDENTIALS'),
    'package_name' => env('PLAY_INTEGRITY_PACKAGE_NAME'),
],
Enter fullscreen mode Exit fullscreen mode

error

8): Failed to get Play Integrity token: PlatformException(INTEGRITY_ERROR, -1: Integrity API error (-1): Integrity API is not available.
I/flutter ( 4308): Integrity API is not enabled, or the Play Store version might be old.
I/flutter ( 4308): Recommended actions:
I/flutter ( 4308): 1) Make sure that Integrity API is enabled in Google Play Console.
I/flutter ( 4308): 2) Ask the user to update Play Store.
I/flutter ( 4308):  (https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE)., null, null)

Enter fullscreen mode Exit fullscreen mode

Solution:

play integrity setup

what is x-play-integrity

Top comments (0)