Below is a small example of how this can be achieved (without client).
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_session/cookies_middleware.dart';
import 'package:shelf_session/session_middleware.dart';
import 'package:shelf_static/shelf_static.dart';
void main(List<String> args) async {
final router = Router();
router.get('/', _handleHome);
router.get('/login', _handleLogin);
router.get('/login/', _handleLogin);
router.post('/login', _handleLogin);
router.post('/login/', _handleLogin);
router.get('/logout', _handleLogout);
router.get('/logout/', _handleLogout);
final staticHandler =
createStaticHandler('web', defaultDocument: 'index.html');
final handler = Cascade().add(staticHandler).add(router).handler;
final pipeline = const Pipeline()
.addMiddleware(logRequests())
.addMiddleware(cookiesMiddleware())
.addMiddleware(sessionMiddleware())
.addHandler(handler);
const address = 'localhost';
const port = 8080;
final server = await io.serve(pipeline, address, port);
print('Serving at http://${server.address.host}:${server.port}');
}
const _menu = '''
<a href="/">Home</a><br />
<a href="/login">Log in</a><br />
<a href="/logout">Log out</a><br />''';
Future<Response> _handleHome(Request request) async {
final userManager = UserManager();
final user = userManager.getUser(request);
var body = '$_menu{{message}}<br />{{cookies}}';
if (user == null) {
body = body.replaceAll('{{message}}', 'You are not logged in');
} else {
body = body.replaceAll('{{message}}', 'You are logged in as ${user.name}');
}
final cookies = request.getCookies();
body = body.replaceAll('{{cookies}}',
cookies.entries.map((e) => '${e.key}: ${e.value}').join('<br />'));
request.addCookie(Cookie('foo', 'Foo'));
request.addCookie(Cookie('baz', 'Baz'));
return _render(body);
}
Future<Response> _handleLogin(Request request) async {
const html = '''
<form action="" method="post">
<label>Login</label><br />
<input name="login" type="text" /><br />
<label>Password</label><br />
<input name="password" type="password" /><br /><br />
<button>Log in</button>
</form>
''';
if (request.method == 'GET') {
return _render(_menu + html);
}
final body = await request.readAsString();
final queryParameters = Uri(query: body).queryParameters;
final login = queryParameters['login'] ?? ''
..trim();
final password = queryParameters['password'] ?? ''
..trim();
if (login.isEmpty || password.isEmpty) {
return _render(_menu + html);
}
final user = User(login);
final userManager = UserManager();
userManager.setUser(request, user);
return Response.found('/');
}
Future<Response> _handleLogout(Request request) async {
Session.deleteSession(request);
return Response.found('/');
}
Response _render(String body) {
return Response.ok(body, headers: {
'Content-type': 'text/html; charset=UTF-8',
});
}
class User {
final String name;
User(this.name);
}
class UserManager {
User? getUser(Request request) {
final session = Session.getSession(request);
if (session == null) {
return null;
}
final user = session.data['user'];
if (user is User) {
return user;
}
return null;
}
User setUser(Request request, User user) {
var session = Session.getSession(request);
session ??= Session.createSession(request);
session.data['user'] = user;
return user;
}
}
For client-server (password
implies password hash):
Server code:
class ClientApi {
final _authenticate = createMiddleware(
requestHandler: (Request request) async {
final headers = request.headers;
final xAuthKey = headers['X-Auth-Key'];
if (xAuthKey is! String) {
return Response(401);
}
final xAuthEmail = headers['X-Auth-Email'];
if (xAuthEmail is! String) {
return Response(401);
}
final connection = await getConnection();
final statement = SelectStatement();
statement.fields.add('id');
statement.fields.add('password');
statement.tables.add('customers');
statement.where.add('id = ?');
final rows = await connection.query('$statement;', [xAuthEmail]);
for (final row in rows) {
final fields = row.fields;
final password = fields['password'] as String;
final apiKey = _getApiKey(password);
if (xAuthKey == apiKey) {
return null;
}
}
return Response(401);
},
);
Handler get handler {
final router = Router();
final routes = {
'login': _login,
};
for (final key in routes.keys) {
final value = routes[key]!;
router.post('/$key', const Pipeline().addHandler(value));
router.post('/$key/', const Pipeline().addHandler(value));
}
final routes2 = {
'add_to_cart': _addToCart,
};
for (final key in routes2.keys) {
final value = routes2[key]!;
router.post('/$key',
const Pipeline().addMiddleware(_authenticate).addHandler(value));
router.post('/$key/',
const Pipeline().addMiddleware(_authenticate).addHandler(value));
}
return router;
}
Future<Response> _login(Request request) async {
final params = await fromJson(request, LoginRequest.fromJson);
final connection = await getConnection();
final name = params.name.toLowerCase();
final statement = SelectStatement();
statement.tables.add('customers');
statement.fields.add('password');
statement.where.add('id = ?');
final rows = await connection.query('$statement;', [name]);
String? password;
for (final row in rows) {
final fields = row.fields;
password = fields['password'] as String;
break;
}
if (password != null && password == params.password) {
final apiKey = _getApiKey(password);
final user = LoginUser(
apiKey: apiKey,
name: name,
);
return toJson(LoginResponse(user: user).toJson());
}
return toJson(LoginResponse(user: null).toJson());
}
}
Client code:
class ClientApi {
void _addAuthHeaders(Map<String, String> headers) {
final user1 = UserService.user.value;
if (user1 != null) {
headers['X-Auth-Key'] = user1.apiKey;
headers['X-Auth-Email'] = user1.name;
}
}
Future<LoginResponse> login({
required String name,
required String password,
}) async {
final request = LoginRequest(
name: name,
password: password,
);
final json = await _post<Map>(
'login',
body: request.toJson(),
);
final response = LoginResponse.fromJson(json);
return response;
}
Future<T> _post<T>(
String path, {
Map<String, String>? headers,
Object? body,
}) async {
final url = Uri.parse('$_host/$path');
headers ??= {};
headers.addAll({
'Content-type': 'application/json',
});
final response = await http.post(
url,
body: jsonEncode(body),
headers: headers,
);
if (response.statusCode != HttpStatus.ok) {
throw StateError('Wrong response status code: ${response.statusCode}');
}
final json = jsonDecode(response.body);
if (json is! T) {
throw StateError('Wrong response');
}
return json;
}
}
Somewhere in the UI component.
void onClick(Event event) {
event.preventDefault();
final bytes = utf8.encode(password);
final digest = sha256.convert(bytes);
Timer.run(() async {
try {
final clientApi = ClientApi();
final response =
await clientApi.login(name: name, password: digest.toString());
final user = response.user;
if (user != null) {
UserService.user.value = User(apiKey: user.apiKey, name: user.name);
}
} catch (e) {
//
}
changeState();
});
}