Protect non-Firebase resources with App Check on iOS

You can protect your app's non-Firebase resources, such as self-hosted backends, with App Check. To do so, you will need to modify your app client to send an App Check token along with each request to your backend, and modify your backend to require a valid App Check token with every request; both tasks are described below.

Before you begin

Add App Check to your app, using either App Attest, DeviceCheck, or a custom provider.

Send App Check tokens with backend requests

To ensure your backend requests include a valid, unexpired, App Check token, wrap each request in a call to AppCheck.token(). The App Check library will refresh the token if necessary, and you can access the token in the method's completion block.

Once you have a valid token, send it along with the request to your backend. The specifics of how you accomplish this are up to you, but don't send App Check tokens as part of URLs, including in query parameters, as this makes them vulnerable to accidental leakage and interception. The following example sends the token in a custom HTTP header, which is the recommended approach.

Swift

AppCheck.appCheck().token(forcingRefresh: false) { token, error in
    guard error == nil else {
        // Handle any errors if the token was not retrieved.
        print("Unable to retrieve App Check token: \(error!)")
        return
    }
    guard let token = token else {
        print("Unable to retrieve App Check token.")
        return
    }

    // Get the raw App Check token string.
    let tokenString = token.token

    // Include the App Check token with requests to your server.
    let url = URL(string: "https://yourbackend.example.com/yourApiEndpoint")!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue(tokenString, forHTTPHeaderField: "X-Firebase-AppCheck")

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        // Handle response from your backend.
    }
    task.resume()
}

Objective-C

[[FIRAppCheck appCheck] tokenForcingRefresh:NO
                                 completion:^(FIRAppCheckToken * _Nullable token,
                                              NSError * _Nullable error) {
    if (error != nil) {
        // Handle any errors if the token was not retrieved.
        NSLog(@"Unable to retrieve App Check token: %@", error);
        return;
    }
    if (token == nil) {
        NSLog(@"Unable to retrieve App Check token.");
        return;
    }

    // Get the raw App Check token string.
    NSString *tokenString = token.token;

    // Include the App Check token with requests to your server.
    NSURL *url = [[NSURL alloc] initWithString:@"https://yourbackend.example.com/yourApiEndpoint"];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"GET"];
    [request setValue:tokenString forHTTPHeaderField:@"X-Firebase-AppCheck"];

    NSURLSessionDataTask *task =
        [[NSURLSession sharedSession] dataTaskWithRequest:request
                                        completionHandler:^(NSData * _Nullable data,
                                                            NSURLResponse * _Nullable response,
                                                            NSError * _Nullable error) {
        // Handle response from your backend.
    }];
    [task resume];
}];

Verify App Check tokens on the backend

In your backend code, if you haven't already installed the Node.js Admin SDK, do so. Then, add logic to your API endpoints that does the following:

  • Check that each request include an App Check token.

  • Verify the App Check token using the Admin SDK's appCheck().verifyToken() method.

    If verification succeeds, verifyToken() returns the decoded App Check token. Successful verification indicates the token originated from an app belonging to your Firebase project.

Reject any request that fails either check. For example, using Express.js middleware:

const express = require('express');
const app = express();

const firebaseAdmin = require('firebase-admin');
const firebaseApp = firebaseAdmin.initializeApp();

const appCheckVerification = async (req, res, next) => {
    const appCheckToken = req.header('X-Firebase-AppCheck');

    if (!appCheckToken) {
        res.status(401);
        return next('Unauthorized');
    }

    try {
        const appCheckClaims = await firebaseAdmin.appCheck().verifyToken(appCheckToken);

        // If verifyToken() succeeds, continue with the next middleware
        // function in the stack.
        return next();
    } catch (err) {
        res.status(401);
        return next('Unauthorized');
    }
}

app.get('/yourApiEndpoint', [appCheckVerification], (req, res) => {
    // Handle request.
});