# Use tokens (/en/realtime-media/voice/build/set-up-token-authentication/use-tokens/flutter)

> 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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-6]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

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

    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-6]

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

    1. Add the following to the `pubspec.yaml` file, under `dependencies`:

       ```yaml
       dependencies:
         # Agora Flutter SDK, use the latest version of agora_rtc_engine
         agora_rtc_engine: ^6.3.0
         # For making http requests
         http: ^0.13.5
       ```

    2. Replace the contents in `/lib/main.dart` with the following code.

       **Sample code for basic authentication**

       ```dart
         import 'dart:convert';

           import 'package:agora_rtc_engine/agora_rtc_engine.dart';
           import 'package:flutter/material.dart';
           import 'package:http/http.dart' as http;

           void main() => runApp(const MyApp());

           /// This widget is the root of your application.
           class MyApp extends StatefulWidget {
           /// Construct the [MyApp]
           const MyApp({Key? key}) : super(key: key);

           @override
           State<MyApp> createState() => _MyAppState();
           }

           class _MyAppState extends State<MyApp> {

           @override
           Widget build(BuildContext context) {
             return MaterialApp(
             theme: ThemeData(
               primarySwatch: Colors.blue,
             ),
             home: Scaffold(
               appBar: AppBar(
                 title: const Text('APIExample'),
               ),
               body: const JoinChannelVideoToken()),
             );
           }
           }

           class JoinChannelVideoToken extends StatefulWidget {
           const JoinChannelVideoToken({Key? key}) : super(key: key);

           @override
           State<StatefulWidget> createState() => _State();
           }

           class _State extends State<JoinChannelVideoToken> {
           late final RtcEngine _engine;
           bool _isReadyPreview = false;

           bool isJoined = false, switchCamera = true, switchRender = true;
           Set<int> remoteUid = {};
           static const String appId = '<Your app ID>'; // Fill in the information from Agora console
           static const String channelId = '<Your channel name>'; // Fill in the channel name
           static const String hostUrl = '<Your host URL and port>'; // Fill in the server URL and port

           @override
           void initState() {
             super.initState();
             _initEngine();
           }

           @override
           void dispose() {
             super.dispose();
             _dispose();
           }

           Future<void> _dispose() async {
             await _engine.leaveChannel();
             await _engine.release();
           }

           Future<void> _initEngine() async {
             _engine = createAgoraRtcEngine();
             await _engine.initialize(const RtcEngineContext(
             appId: appId,
             ));

             _engine.registerEventHandler(RtcEngineEventHandler(
             onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
               setState(() {
               isJoined = true;
               });
             },
             onUserJoined: (RtcConnection connection, int rUid, int elapsed) {
               setState(() {
               remoteUid.add(rUid);
               });
             },
             onUserOffline:
               (RtcConnection connection, int rUid, UserOfflineReasonType reason) {
               setState(() {
               remoteUid.removeWhere((element) => element == rUid);
               });
             },
             onLeaveChannel: (RtcConnection connection, RtcStats stats) {
               setState(() {
               isJoined = false;
               remoteUid.clear();
               });
             },
             onTokenPrivilegeWillExpire: (RtcConnection connection, String token) {
               _fetchToken(1234, channelId, 1, false);
             },
             onRequestToken: (RtcConnection connection) {
               _fetchToken(1234, channelId, 1, true);
             },
             ));

             await _engine.enableVideo();

             await _engine.startPreview();
             await _fetchToken(1234, channelId, 1, true);

             setState(() {
             _isReadyPreview = true;
             });
           }

           Future<void> _fetchToken(
             int uid,
             String channelName,
             int toeknRole,
             bool needJoinChannel,
           ) async {
             var client = http.Client();
             try {
             Map<String, String> headers = {
               'Content-type': 'application/json',
               'Accept': 'application/json',
             };

             var response = await client.post(Uri.parse(hostUrl),
               headers: headers,
               body: jsonEncode(
                 {'uid': uid, 'ChannelName': channelName, 'role': toeknRole}));
             var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;

             final token = decodedResponse['token'];
             if (needJoinChannel) {
               await _engine.joinChannel(
               token: token,
               channelId: channelName,
               uid: uid,
               options: const ChannelMediaOptions(
                 channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
                 clientRoleType: ClientRoleType.clientRoleBroadcaster,
               ),
               );
             } else {
               await _engine.renewToken(token);
             }
             } finally {
             client.close();
             }
           }

           @override
           Widget build(BuildContext context) {
             if (!_isReadyPreview) return Container();
             return Stack(
             children: [
               AgoraVideoView(
               controller: VideoViewController(
                 rtcEngine: _engine,
                 canvas: const VideoCanvas(uid: 0),
               ),
               ),
               Align(
               alignment: Alignment.topLeft,
               child: SingleChildScrollView(
                 scrollDirection: Axis.horizontal,
                 child: Row(
                 children: List.of(remoteUid.map(
                   (e) => SizedBox(
                   width: 120,
                   height: 120,
                   child: AgoraVideoView(
                     controller: VideoViewController.remote(
                     rtcEngine: _engine,
                     canvas: VideoCanvas(uid: e),
                     connection: const RtcConnection(channelId: channelId),
                     ),
                   ),
                   ),
                 )),
                 ),
               ),
               )
             ],
             );
           }
           }
       ```

       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="info">
      <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-6]

    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-5]

    * [renewToken](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_renewtoken)

    * [onTokenPrivilegeWillExpire](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_ontokenprivilegewillexpire)

    * [onConnectionStateChanged](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onconnectionstatechanged)

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
