Autostrada
Autostrada
Create a new codebase Get Autostrada Plus
Documentation Changelog Roadmap Give feedback
Login

DocumentationTraditional web application › User accounts and authentication

User accounts and authentication

If you enable the User accounts and authentication option when generating your codebase, the application will be configured to support user accounts with email/password authentication and ready-to-use signup, login, logout and password-reset workflows.

The generated code follows good security practices, with password hashing, constant-time password comparison, CSRF protection, secure password reset flows, and session management aligned with OWASP recommendations.

The following routes are included in the generated codebase:

Route What it does
GET /restricted Displays an example page that is only accessible to authenticated (logged-in) users.
GET /signup Displays the signup form.
POST /signup Validates the submitted email and password, creates the user account, logs the user in, and redirects to /restricted.
GET /login Displays the login form.
POST /login Validates the submitted credentials, logs the user in, and redirects either to the originally requested page or /restricted.
POST /logout Logs the user out and redirects to /.
GET /forgotten-password Displays the forgotten password form.
POST /forgotten-password Validates the submitted email address, creates a password reset token, sends the reset email, and redirects to /forgotten-password-confirmation.
GET /forgotten-password-confirmation Displays a confirmation page after a password reset email is sent.
GET /password-reset/{plaintextToken} Validates the password reset token and displays the password reset form. Shows an invalid link error if the token is invalid or expired.
POST /password-reset/{plaintextToken} Validates and updates the user’s password, deletes reset tokens, and redirects to /password-reset-confirmation.
GET /password-reset-confirmation Displays a confirmation page after the password has been reset.
The Users database model

A User struct describing the data for a user is defined in internal/database/users.go.

type User struct {
    ID             int       `db:"id"`
    Created        time.Time `db:"created"`
    Email          string    `db:"email"`
    HashedPassword string    `db:"hashed_password"`
}

User records are stored in a users database table. You can find the SQL migration for creating this table in assets/migrations/000003_create_users_table.up.sql.

The internal/database/users.go also contains the following methods for interacting the user data:

Method Description
InsertUser() Inserts a new user record into the users table using the supplied email address and hashed password, and returns the newly-created user ID.
GetUser() Retrieves a user from the users table using its ID. It returns a User struct, along with a boolean indicating whether the user record was found.
GetUserByEmail() Retrieves a user from the users table using its email address with a case-insensitive lookup. It returns a User struct, along with a boolean indicating whether the user record was found.
UpdateUserHashedPassword() Updates the hashed password for an existing user identified by ID.

You can call these methods from your handlers or middleware any time you need them. For example:

func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
	...

    email := "me@example.com"

	user, found, err := app.db.GetUserByEmail(email)
	if err != nil {
		app.serverError(w, r, err)
		return
	}
	if !found {
		fmt.Fprintf(w, "User not found: %s", email)
		return
	}

	fmt.Fprintf(w, "User found: %s (id=%d)", user.Email, user.ID)
}

Feel free to add additional fields to the User struct, and any additional functions to the internal/database/users.go that you need.

Signup

The code for managing user signup operations can be found in the signup() handler in the cmd/web/handlers.go file. There are a few things to note:

If any of the validation checks fail, the signup form will be displayed with the appropriate error messages.

The HTML template for the signup page is in the assets/templates/pages/signup.tmpl file. Feel free to customize it to meet your needs.

Login

The code for managing user login operations can be found in the login() handler in the cmd/web/handlers.go file.

When a login with a valid email and password is successful, the user ID is stored in a authenticatedUserID value in the server-side session. The session cookie (containing a random session ID value only) is sent back to the client.

By default, the session cookie is valid for 1 week – meaning that a user will remain logged in for up to one week. You can change this value in cmd/web/main.go if you wish (see the server-side sessions documentation for more information).

The HTML template for the login page is in the assets/templates/pages/login.tmpl file. Again, feel free to customize it to meet your needs.

Authentication and authorization

When the session cookie is sent back with subsequent requests to the application, the authenticate() middleware in the cmd/web/middleware.go file retrieves the authenticatedUserID value from the session, looks up the user's details in the database, and stores them in the request context.

You can control access to specific handlers based on whether a user is authenticated (i.e. logged-in) or not using the requireAuthenticatedUser() and requireAnonymousUser() middleware on specific routes.

An example of using these can be seen in the cmd/web/routes.go file. By default, the generated application will be configured to use the following routes:

Route Access
GET /static/ Public
GET / Public
GET /signup
POST /signup
Anonymous (not logged in) users only
GET /login
POST /login
Anonymous (not logged in) users only
GET /forgotten-password
POST /forgotten-password
Anonymous (not logged in) users only
GET /forgotten-password-confirmation Anonymous (not logged in) users only
GET /password-reset/{plaintextToken}
POST /password-reset/{plaintextToken}
Anonymous (not logged in) users only
GET /password-reset-confirmation Anonymous (not logged in) users only
GET /restricted Authenticated (logged in) users only
POST /logout Authenticated (logged in) users only
Accessing the current user data

You can retrieve the details of the current authenticated user in your handlers, helpers and middleware by calling the contextGetAuthenticatedUser() function, which is defined in cmd/web/context.go. You call it like this:

func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
    authenticatedUser, found := contextGetAuthenticatedUser(r)
    ...
}

If the HTTP request is coming from an authenticated (i.e. logged in) user, this will return a User struct containing the authenticated user's details, and a value of true.

If the HTTP request is not coming from an authenticated (i.e. logged in) user, this will return an empty User struct and a value of false.

For authenticated users, their information is also automatically available in your HTML templates via {{.AuthenticatedUser}}. This will be nil if the current user is anonymous (not logged in), meaning that you can check if a user is logged in with {{if .AuthenticatedUser}}...{{end}} in your HTML templates. An example of this can be seen in the assets/templates/partials/nav.tmpl file:

{{if .AuthenticatedUser}}
    <form method="POST" action="/logout">
        <input type='hidden' name='csrf_token' value='{{.CSRFToken}}'>
        {{.AuthenticatedUser.Email}}
        <button class="link">Logout</button>
    </form>
{{else}}
    <a href="/signup">Signup</a>
    <a href="/login">Login</a>
{{end}}
Logout

The code for managing user logout operations can be found in the logout() handler in the cmd/web/handlers.go file.

This removes the authenticatedUserID value from the session, and redirects the user to the application homepage. Feel free to change the redirect location if you wish.

The logout is triggered from the form in the assets/templates/partials/nav.tmpl file.

Password reset

The code for managing password reset operations can be found in the forgottenPassword(), forgottenPasswordConfirmation(), passwordReset() and passwordResetConfirmation() handlers in the cmd/web/handlers.go file.

The forgottenPassword() handler displays a form which asks the user to input their email address. When the form is submitted, the handler performs a case-insensitive lookup on the address. If a matching user is found, a secure password reset token is generated. A hashed version of the token is stored in the password_resets database table, and the user is sent an email containing a link (containing the token) that they can click. The user is then redirected to a confirmation page, which is rendered by the forgottenPasswordConfirmation() handler.

The HTML template for the forgotten password page is in the assets/templates/pages/forgotten-password-confirmation.tmpl file, and the template for the confirmation page is in assets/templates/pages/forgotten-password-confirmation.tmpl.

The token is valid for 24 hours. You can change this if you want by editing the ttl argument when app.db.InsertPasswordReset() is called in the forgottenPassword() handler.

When the user clicks the link in their email, the HTTP request is handled by the passwordReset() handler. This extracts the token from link URL, hashes it, and checks to see if a matching and valid token exists in the password_resets table. If it doesn't, the user is shown an error message. If it does, the user is shown a form to input their new password.

When the form is submitted, the new password is validated (using the same rules as the signup() handler), hashed using Bcrypt, and updated in the database. The user is then redirected to a confirmation page, which is rendered by the passwordResetConfirmation() handler.

The HTML template for the password reset page is in the assets/templates/pages/password-reset-confirmation.tmpl file, and the template for the confirmation page is in the assets/templates/pages/password-reset-confirmation.tmpl.

Important: For the password reset functionality please make sure that the application is correctly configured to support sending emails.

Password hashing

Passwords are hashed using Bcrypt with a cost of 12. You can change the cost if you want by editing the Hash() function in the internal/password/hash.go file.

This file also includes a Matches() function, which you can use to verify that a plaintext password matches a specific hashed password.

CSRF prevention

The preventCSRF() middleware in the cmd/web/middleware.go file is used on all application routes to block CSRF attacks. This middleware provides a thin wrapper around the excellent justinas/nosurf package.

When you are writing you own HTML forms that issue POST requests, you need to include a CSRFToken so that the preventCSRF() middleware can verify that the request is coming from an permitted source. The normal way to do this is by including a hidden input in your forms with the name csrf_token like so:

<form method="POST" action="/example">
    <input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
    ...
</form>

You can see an example of this in practice in the signup, login, logout and password reset forms.

If a POST request is received without a valid CSRF token in the form data, the preventCSRF() middleware will send the client a 400 Bad Request response with the message "CSRF token validation failed". Feel free to edit the preventCSRF() middleware to change this if you want.