# Use tokens (/en/realtime-media/video/build/authenticate-users/authentication-workflow/react-native)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

To protect your business, it is best practice to authenticate every client that joins a channel. This guide explains how to fetch an authentication token from your token server, use it to join a channel, and renew the token when it expires.

## Understand the tech [#understand-the-tech]

When a user attempts to connect to an Agora channel, your app retrieves a token from the token server in your security infrastructure. Your app then sends this token to Agora SDRTN® for authentication. Agora SDRTN® reads the information stored in the token to validate the request.

The following figure shows the call flow you implement to create step-up-authentication with Agora Video Calling:

<Accordions>
  <Accordion title="Token authentication flow">
    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)
  </Accordion>
</Accordions>

## Prerequisites [#prerequisites]

Before starting, ensure that you have:

* Implemented the [Quickstart](/en/realtime-media/video/get-started-sdk) in your project.

* Deployed a token server using either of the following guides:

  * [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server)
  * [Deploy a middleware server](/en/realtime-media/video/build/authenticate-users/middleware-token-server)

## Implement basic authentication [#implement-basic-authentication]

This section shows you how to implement basic authentication by acquiring a token and using it to join a channel.

      
  
      
  
      
  
      
  
      
  
      
  
      
    ### Use a token to join a channel [#use-a-token-to-join-a-channel-5]

    This section shows you how to integrate token authentication in your app.

    1. Open the [SDK quickstart](/en/realtime-media/video/get-started-sdk) project you created earlier.

    2. In `ProjectName.tsx` replace the contents with the following:

    **Sample code for basic authentication**

    ```js
    import React, { useCallback, useEffect, useRef, useState } from 'react';
        import {
            PermissionsAndroid,
            Platform,
            SafeAreaView,
            ScrollView,
            StyleSheet,
            Switch,
            Text,
            View,
        } from 'react-native';

        import {
            ChannelProfileType,
            ClientRoleType,
            createAgoraRtcEngine,
            IRtcEngine,
            IRtcEngineEventHandler,
            RtcConnection,
            RtcSurfaceView,
        } from 'react-native-agora';

        // Define basic information
        const appId = '<Your app ID>';
        const channelName = 'test';
        const uid = 123; // Local user Uid

        const App = () => {
        const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine instance
        const eventHandlerRef = useRef<IRtcEngineEventHandler>(); // IRtcEngine event handler
        const [token, setToken] = useState<string>(''); // RTC Token
        const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
        const [isHost, setIsHost] = useState(true); // User role
        const [remoteUid, setRemoteUid] = useState(0); // Uid of remote user
        const [message, setMessage] = useState(''); // Message to the user

        // Send a request to obtain a token to the token server
        const fetchToken = useCallback(() => {
            // Token server URL example: http://12.123.1.123:8082/fetch_rtc_token
            const url = '<Your host URL and port>/fetch_rtc_token';
            const body = JSON.stringify({
            uid,
            ChannelName: channelName,
            role: isHost
                ? ClientRoleType.ClientRoleBroadcaster
                : ClientRoleType.ClientRoleAudience,
            });
            console.log('fetchToken', url, body);
            return fetch(url, {
                method: 'POST',
                body,
            })
            .then((res) => res.json())
            .then((res) => {
                showMessage('token ' + res.token + 'Already refreshed');
                if (+res.code === 200) {
                setToken(res.token);
                }
                return res;
            });
            }, [isHost]);

        // Use the obtained token to join the channel
        const join = useCallback(async () => {
            if (!token) {
                await fetchToken();
            } else {
            try {
                // After joining the channel, set the channel profile to live broadcast
                agoraEngineRef.current?.setChannelProfile(
                ChannelProfileType.ChannelProfileLiveBroadcasting
                );
                if (isHost) {
                agoraEngineRef.current?.startPreview();
                // Join the channel as a host
                agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                    publishCameraTrack: true,
                    clientRoleType: ClientRoleType.ClientRoleBroadcaster,
                });
                } else {
                // Join the channel as audience
                agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                    clientRoleType: ClientRoleType.ClientRoleAudience,
                });
                }
            } catch (e) {
                console.log(e);
            }
        }
        }, [fetchToken, isHost, token]);

        const leave = () => {
            try {
                agoraEngineRef.current?.leaveChannel();
                setRemoteUid(0);
                setIsJoined(false);
                showMessage('Left the channel');
            } catch (e) {
                console.error(e);
            }
        };

        useEffect(() => {
            const setupVideoSDKEngine = async () => {
            try {
                if (Platform.OS === 'android') {
                await getPermission();
                }
                agoraEngineRef.current = createAgoraRtcEngine();
                eventHandlerRef.current = {
                onJoinChannelSuccess: () => {
                    showMessage('Successfully joined the channel: ' + channelName);
                    setIsJoined(true);
                },
                onUserJoined: (_connection, Uid) => {
                    showMessage('Remote user ' + Uid + ' joined');
                    setRemoteUid(Uid);
                },
                onUserOffline: (_connection, Uid) => {
                    showMessage('Remote user ' + Uid + ' left the channel');
                    setRemoteUid(0);
                },
                onTokenPrivilegeWillExpire(connection: RtcConnection, token: string) {
                    showMessage('token ' + token + 'Expires soon');
                    fetchToken();
                },
                onRequestToken(connection: RtcConnection) {
                    showMessage('token expired');
                    setToken('');
                },
                };
                const agoraEngine = agoraEngineRef.current;
                //Initialize engine
                agoraEngine.initialize({
                    appId: appId,
                    channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                });
                // Register event handler
                agoraEngine.registerEventHandler(eventHandlerRef.current);
                // Start local video
                agoraEngine.enableVideo();
            } catch (e) {
                console.error(e);
            }
        };

            setupVideoSDKEngine();

            return () => {
                agoraEngineRef.current?.release();
            };
        }, []); // eslint-disable-line react-hooks/exhaustive-deps

        useEffect(() => {
            const checkJoined = () => {
            if (isJoined) {
                console.log('renewToken');
                // agoraEngineRef.current?.renewToken(token);
            } else {
                console.log('joinChannel');
                join();
            }
            };
            checkJoined();
        }, [token]); // eslint-disable-line react-hooks/exhaustive-deps

        // Render the user interface
        return (
            <SafeAreaView style={styles.main}>
            <Text style={styles.head}>Agora SDK Quickstart</Text>
            <View style={styles.btnContainer}>
                <Text onPress={join} style={styles.button}>
                Join channel
                </Text>
                <Text onPress={leave} style={styles.button}>
                Leave Channel
                </Text>
            </View>
            <View style={styles.btnContainer}>
                <Text>Audience</Text>
                <Switch
                onValueChange={(switchValue) => {
                    setIsHost(switchValue);
                    if (isJoined) {
                    leave();
                    }
                }}
                value={isHost}
                />
                <Text>Host</Text>
            </View>
            <ScrollView
                style={styles.scroll}
                contentContainerStyle={styles.scrollContainer}
            >
                {isJoined ? (
                <React.Fragment key={0}>
                    <RtcSurfaceView canvas={{ uid: 0 }} style={styles.videoView} />
                    <Text>Local user uid: {uid}</Text>
                </React.Fragment>
                ) : (
                <Text>Join a channel</Text>
                )}
                {isJoined && !isHost && remoteUid !== 0 ? (
                <React.Fragment key={remoteUid}>
                    <RtcSurfaceView
                    canvas={{ uid: remoteUid }}
                    style={styles.videoView}
                    />
                    <Text>Remote user uid: {remoteUid}</Text>
                </React.Fragment>
                ) : (
                <Text>{isJoined && !isHost ? 'Wait for a remote user to join' : ''}</Text>
                )}
                <Text style={styles.info}>{message}</Text>
            </ScrollView>
            </SafeAreaView>
        );

        // Show message
        function showMessage(msg: string) {
            console.info(msg);
            setMessage(msg);
        }
        };

        // Define user interface style
        const styles = StyleSheet.create({
            button: {
                paddingHorizontal: 25,
                paddingVertical: 4,
                fontWeight: 'bold',
                color: '#ffffff',
                backgroundColor: '#0055cc',
                margin: 5,
            },
            main: { flex: 1, alignItems: 'center' },
            scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
            scrollContainer: { alignItems: 'center' },
            videoView: { width: '90%', height: 200 },
            btnContainer: { flexDirection: 'row', justifyContent: 'center' },
            head: { fontSize: 20 },
            info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
        });

        const getPermission = async () => {
            if (Platform.OS === 'android') {
                await PermissionsAndroid.requestMultiple([
                'android.permission.RECORD_AUDIO',
                'android.permission.CAMERA',
                ]);
            }
        };

        export default App;
    ```

    Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `<Your host URL and port>` with the host URL and port of the local Golang server you have deployed. For example `99.9.9.99:8082`.

    The sample code implements the following logic:

    * Calls `joinChannel` to join a channel using the user ID, the channel name, and a token you obtain from the server. The user ID and channel name you specify must be consistent with the values you used to generate the token.

    * The SDK triggers an `onTokenPrivilegeWillExpire` callback 30 seconds before the token expires. After receiving the callback, you obtain a new token from the server and call `renewToken` to pass the newly generated token to the SDK.

    * If the token expires, the SDK triggers an `onRequestToken` callback. After receiving the callback, obtain a new token from the server and call `joinChannel` with the new token to rejoin the channel.

    Build and run the project on the local device, the app performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    
  
      
  
      
  
      
  
      
  
      
  
<CalloutContainer type="warning">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    The user ID and channel name used to join a channel must be consistent with the values used to generate the token.
  </CalloutDescription>
</CalloutContainer>

## Reference [#reference]

This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

      
  
      
  
      
  
      
  
      
  
      
    ### API reference [#api-reference-3]

    * [renewToken](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_renewtoken)
    * [onTokenPrivilegeWillExpire](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_ontokenprivilegewillexpire)
    * [onRequestToken](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onrequesttoken)

    
  
      
  
      
  
      
  
      
  
      
  
