Seaside snippets: accepting logins from external pages


This is just another small learning about Smalltall that I hope is helpful for somebody. Nothing rocket-sciency, but a little thing that needs to be experimented with to know how it might work. I am happy to learn about improvements, better approaches or nicer solutions to the problem.

So here I am, explaining the problem. Seaside as a web server tends to feel like a monolith. Everything is controlled by Seaside and it sometimes seems to be complicated to integrate it with other technologies. This may or may not be a problem you mostly have when you come to Web programming from a classic fat client world and have had no or little exposure to web technologies before doing web development, or, to put it less negative: when you start web programming by using Seaside and Smalltalk rather than coming from a cool framework like React, Angular, VueJS or whatever.

The classical approach to having a web application which requires the user to log on before information is presented to her is to make sure the first page a visitors sees is a Login Component which only #show:’s any pages to a user once the user has entered valid credentials. The usual implementation here is to register this Login component as the root Component of your Seaside Application.

This is all fine and has been working for years.

But most web applications, at least when they are open to the public and represent a business (like Kontolino! for example), are accompanied by a web site promoting the thing. There are a few pages like “about us”, “pricing”, “imprint”, “security”, you name it. And of course there needs to be a link to the application somewhere on this page, inviting visitors to register and returning visitors to log in and start using your application.

So you may want to add a login/password dialog to this web site that directly logs teh user in to your Seaside Application. Just drop down a little form with two input fields and a login button that directly sends you to your shiny dashboard and welcome page in the application.

Since most of the times this web site is unlikely to be served by your Seaside Application but rather as static pages or some CMS like wordpress, typo3 or whatever, there is no chance to make your Seaside Application to render its Login screen inside that page.

Unless you use tricks like html embeds or iframes that request the login form from the Seaside Application. This approach, however has a few negative drawbacks, like the fact that each time the embed gets requested, Seaside will have to provide its Login form, which most likely means instantiating a WASession, and of course using CPU cycles within your Smalltalk image that should be busy serving all the connected users and not spend a certain percentage of its power to draw two input fields and a submit button.

Of course, there is a better way: let the CMS or static web pages do the job of presenting the input fields for logon and only let Seaside get into action when a user really attempts to log in.

Loose coupling is one of the strengths of the web, and this task per se is super easy for the html part of it. All you need is an html form with the two input fields for login credentials and a submit button that posts the form to your Seaside Application. Let’s have a short look at an example:

<form action="https://mySeasideApp.com:8080/?login" id="login" method="post">
  <label class="h2" form="person">Please login:</label>
  <label for="usernam">User name</label>
  <input type="text" name="username" id="username" >

  <label for="passwd">Password</label>
  <input type="password" name="passwd" id="passwd" >

  <button type="submit">Log In!</button>
</form>

There’s nothing shockingly exciting about this html snippet. Just a form whose action is you Seaside Application’s url and some parameter that indicates the user wants to login. This parameter is not even necessary, but I like to differntiate a little on the server side to present different login info on the welcome screen. Or maybe you want to log where people come from or whatever, so this ‘?login’ url parameter is probably helpful. The important thing to note here is that you should make the method a POST and also make sure you don’t accept logins if they are not POSTed. The most important thing is that in a POST request, the password will not be transported to you application as part of the URL, but in the request body instead.

So nothing fancy so far, I know. I didn’t say this will be blog post that changes your life.

The most exciting thing in this post is that you need to know how to accept logins on the Seaside server without having rendered the login from at first and not processing the credentials in a Callback.

This is where the method #initialRequest: comes in. It is implemented in WAPresenter and therefor is available in any WAComponent. Seaside sends the method to your root component whenever a request comes in and needs to be handled by a Seaside component. So the first thing to do is to implement your own #initialRequest: in your root Component. The Root component usually is the one you registered with WAAdmin when you started your Seaside app.

#initialRequest: gets invoked before anything is rendered and gets a WARequest object as a parameter. In it you will find all the info that came in in the HTTP request, like post fields, the url, the method and all that stuff.

Here, finally, is how you’d handle the login credentials in Seaside from that HTTP request:

initialRequest: request

initialRequest: request
  super initialRequest: request.
 
request at: 'login' ifPresent: [:para | ^self attemptLogonWithRequest: request].

Note: in the block we igone the parameter, since we don’t expect any url parameters to the ‘?login’ part.

anmeldenMitRequest: req

attemptLoginWithRequest: req
  | uname pw |

  "The name of the input field in the html is the name of the field in the request"
  uname := req at: 'username' ifAbsent: []. 
  pw := req fields at: 'passwd' ifAbsent: [].

  (req isPost and: [uname notNil and: [uname notNil]])
      ifTrue: [self session searchForUser: ben withPw: pw].

  self session user isNil "this is being set in #serchForUser:withPw:"
      ifFalse: [self showDashboard]
      ifTrue: [
        self
            show: (MyExistingLoginComponent new username: uname)
            onAnswer: [:success | success ifTrue: [self showDashboard]]]

That’s basically all. As I said: no rocket science. The only real information here is that you can use #initialRequest: to do some checks on the HTTP request before you do anything. You can redirect the user to certain components, check for credentials and other conditions etc.

I hope this is useful to anybody and I am looking forward to reading your comments if you know better options and have implemented neat tricks using initialRequest:.

4 Comments

  1. Julián says:

    You read my mind! Had a Seaside App in production and CPU/Ram was skyrocketing because of bots simply going into the homepage and creating unnecessary sessions. It’ll need a little vhost tweaking but this is the way to go. Thanks!

    1. Joachim says:

      Thanks for your feedback, Julian. I hope this is a helpful starting point for you. And feel free to post about your experience later – either as a comment here or maybe on your own blog, medium, whatever. I think we ned more such small pieces, even if they are not revolutionary

  2. Nicholas Gilman says:

    Nice article, Joachim!

    1. Joachim says:

      thanks Nick, and merry christmas!

Comments are closed.