1

I'm trying to keep users logged in with nestjs backend, when I use Postman the process works very smoothly, but with Flutter I don't know how to do it. I don't think that I actually understand how sessions work for mobiles, I tried looking for some proper explaining but I couldn't find anything so far.

Nestjs Code

 @Controller('users')
@Serialize(UserDto)
export class UsersController {
  constructor(
    private usersService: UsersService,
    private authService: AuthService,
  ) {}

  @Get('/whoami')
  @UseGuards(AuthGuard)
  whoAmI(@currentUser() user: User) {
    return user;
  }

  @Get()
  getUsers(@Query('email') email: string) {
    return this.usersService.find(email);
  }

  @Post('/signup')
  async sendUser(@Body() body: UserDto, @Session() session: any) {
    console.log(body);
    const user = await this.authService.signup(body.email, body.password);
    session.userId = user.id;
    console.log(session.userId);
    return user;
  }

  @Post('/signin')
  async signin(@Body() body: UserDto, @Session() session: any) {
    const user = await this.authService.signin(body.email, body.password);
    session.userId = user.id;
    console.log(session.userId);
    return user;
  }

  @Post('/signout')
  async signout(@Session() session: any) {
    console.log(session.userId);
    if (!session.userId) {
      throw new NotFoundException('user not found');
    }
    session.userId = null;
  }
}

Flutter Code

Future<void> signin(
      String username, String password, BuildContext context) async {
    try {
      var url = 'https://example-app.herokuapp.com/users/signin';
      var dio = Dio();
      var response =
          await dio.post(url, data: {'email': username, 'password': password}, options: Options(headers: {'Accept': 'application/json'}));
      
      print(response.headers);

      // response;
      Navigator.of(context).pushNamed(CategoryDetailScreen.routeName);
    } catch (err) {
      print(err);
      throw err;
    }
  }

Future<void> signout() async {
    try {
      var url = 'https://example-app.herokuapp.com/users/signout';
      var dio = Dio();
      var response = await dio.post(url, 
          options: Options(headers: {
            'cookie':
                'key'
          }
          )
          );
      print(response.headers);
      response;
      // return response;
    } catch (err) {
      print(err);
      throw err;
    }
  }
Khaled
  • 23
  • 1
  • 6
  • Check out session management with cookies: https://stackoverflow.com/questions/50299253/flutter-http-maintain-php-session/50299669#50299669 – Richard Heap Dec 29 '21 at 21:00
  • @RichardHeap thank you, that was very useful, I managed to get the cookie and save it to the device. Although, what's remaining is how to let flutter know that the user is still logged in even after they changed apps or closed the app. – Khaled Jan 02 '22 at 08:02
  • Save the cookie to shared preferences? – Richard Heap Jan 02 '22 at 14:54

1 Answers1

0

Thanks to @RichardHeap 's comment I managed to solve my issue.

Check out session management with cookies: Flutter http Maintain PHP session

I used FlutterSecureStorage package to store the incoming cookie and then decide which screen to show as a home screen using FutureBuilder as seen below:

I used these functions to write cookies and delete them from the device:

    Future<void> signin(
          String username, String password, BuildContext context) async {
        try {
          // String cookie = '';
          var url = 'https://daleel-app.herokuapp.com/users/signin';
          var storage = FlutterSecureStorage();
          var dio = Dio();
          var response =
              await dio.post(url, data: {'email': username, 'password': password});
          List<String>? cookies = response.headers['set-cookie'];
          for (int i = 0; i <= cookies!.length - 1; i++) {
            var cokIndex = cookies[i].indexOf(';');
            var subCookies = cookies[i].substring(0, cokIndex + 1);
            cookie += subCookies + ' ';
          }
          var subbedCookie = cookie.substring(0, cookie.length - 2);
          print(subbedCookie);
    
          storage.write(key: 'cookie', value: subbedCookie);
          loggedIn = true;
          // print(response.headers['set-cookie']);
          // [express:sess=eyJ1c2VySWQiOjU1fQ==; path=/; httponly, express:sess.sig=Zy_Lc7kXM1BqZKIZRRt7ygpCTrM; path=/; httponly]
          Navigator.of(context).pushNamed(CategoryDetailScreen.routeName);
        } catch (err) {
          print(err);
          throw err;
        }
      }

Future<void> signout(BuildContext context) async {
    try {
      var url = 'https://daleel-app.herokuapp.com/users/signout';
      var dio = Dio();
      var fStorage = FlutterSecureStorage();
      var header = await fStorage.read(key: 'cookie');
          await dio.post(url, options: Options(headers: {'cookie': header}));
          fStorage.delete(key: 'cookie');
          Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
          print('you reached here');
    } catch (err) {
      print(err);
      throw err;
    }
  }

And here I used the FutureBuilder to decide which screen to show:

class MyApp extends StatefulWidget {

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
    var cookie = FlutterSecureStorage().read(key: 'cookie');
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (ctx) => Places(),
        ),
        ChangeNotifierProvider(
          create: (ctx) => Offers(),
        ),
      ],
      child: FutureBuilder(
        future: cookie,
        builder: (BuildContext context, AsyncSnapshot snapshot) => MaterialApp(
            debugShowCheckedModeBanner: true,
            title: 'Daleel',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: snapshot.hasData ? HomeScreen() : LoginScreen(),
            routes: {
              LoginScreen.routeName: (ctx) => LoginScreen(),
              TestScreen3.routeName: (ctx) => TestScreen3(),
              TestScreen2.routeName: (ctx) => TestScreen2(),
              HomeScreen.routeName: (ctx) => HomeScreen(),
              DetailsScreen.routeName: (ctx) => DetailsScreen(),
              ExploreScreen.routeName: (ctx) => ExploreScreen(),
              CategoryDetailScreen.routeName: (ctx) => CategoryDetailScreen(),
              SearchDetailsScreen.routeName: (ctx) => SearchDetailsScreen(),
            }),
      ),
    );
  }
}
Khaled
  • 23
  • 1
  • 6