Dart shelf – Middleware and handler execution order


I’m lost trying to understand how Dart shelf executes the middleware and handlers. From all the documentation I have read (and briefing it up) if you write a Middleware that returns null, then the execution goes down the pipeline.
Otherwise if the middleware returns a Response, then the execution down the pipeline is stopped, and the Response is returned to the caller.

I have a server with a simple pipeline like this:

    var handler = const shelf.Pipeline()

The auth middleware checks 3 cases: Register, Login and Verify.

  • Register -> Creates new user and returns Response.ok(token), or if nor possible Response.InternalServerError
  • Login -> Refreshes the token and returns Response.ok(token), or if not correct Response(401)
  • Verify -> Returns null when ok(should continue down the pipeline), or Response(403, forbidden)

The problem is, that I cannot stop the execution of the middlewares. If I make a successful login, still the program goes down the pipeline and calls the Router. Which of course doesn’t have the path for register and returns 404 as it is expected to do.

According to shelf documentation, it is supposed to stop when a middleware returns a response. What the hell am I doing wrong?

This is the code of the auth Middleware for reference:

    abstract class AuthProvider {
      static JsonDecoder _decoder = const JsonDecoder();
      static FutureOr<Response> handle(Request request) async {
        print('Entering auth middleware');
        if(request.url.toString() == 'login'){
          print('into login from auth');
        else if(request.url.toString() == 'register'){
          print('Into register from auth');
        else {
          print('Into verify from auth');
      static FutureOr<Response> auth(Request request) async {
        print('Entering auth');
        String sql;
        var query = ExecQuery();
        try {
          dynamic data = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
          final user = data['email'].toString();
          final hash = Hash.create(data['password'].toString());
          sql =
          '''SELECT COUNT(*) FROM public.user WHERE (email = '${user}' AND password = '${hash}')''';
          await query.countSql(sql);
          if (query.result.status && query.result.opResult[0][0] == 1) {
            JwtClaim claim = JwtClaim(
              subject: user,
              issuer: 'Me',
              audience: ['users'],
            final token = issueJwtHS256(claim, config.secret);
            sql = '''UPDATE public.user SET token = '${token}'
              WHERE (email = '${user}' AND password = '${hash}')''';
            await query.rawQuery(sql);
            return Response.ok(token);
          else{throw Exception();}
        } catch (e) {
          return Response(401, body: 'Incorrect username/password');
      static FutureOr<Response> verify(Request request) async {
        print('Entering verify');
        try {
          final token = request.headers['Authorization'].replaceAll('Bearer ', '');
          print('Received token: ${token}');
          final claim = verifyJwtHS256Signature(token, config.secret);
          print('got the claim');
          claim.validate(issuer: 'ACME Widgets Corp',
              audience: 'homacenter');
          print ('returning null in middleware');
          return null;
        } catch(e) {
          return Response.forbidden('Authorization rejected');


I reply myself… after losing days in this, a return was missing, that made the pipeline keep going. Issue closed.

abstract class AuthProvider {
  static JsonDecoder _decoder = const JsonDecoder();

  static FutureOr<Response> handle(Request request) async {
    if(request.url.toString() == 'login'){
      return AuthProvider.auth(request);
    else if(request.url.toString() == 'register'){
      return RegisterController.handle(request);
    else {
      return AuthProvider.verify(request);

Answered By – 0ver0n

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.