go 64 lines · 9 steps

A cookie-backed signup wizard in Gin

A multi-step signup form keeps its whole state in one signed, encrypted cookie instead of server-side sessions.

Explained by highlit
1package signup
2 
3var wizardCodec = securecookie.New(
4 []byte(os.Getenv("WIZARD_HASH_KEY")),
5 []byte(os.Getenv("WIZARD_BLOCK_KEY")),
6)
7 
8type WizardState struct {
9 Step int `json:"step"`
10 Email string `json:"email"`
11 Company string `json:"company"`
12 Plan string `json:"plan"`
13}
14 
15func loadState(c *gin.Context) WizardState {
16 var st WizardState
17 if raw, err := c.Cookie("signup_wizard"); err == nil {
18 _ = wizardCodec.Decode("signup_wizard", raw, &st)
19 }
20 return st
21}
22 
23func saveState(c *gin.Context, st WizardState) error {
24 encoded, err := wizardCodec.Encode("signup_wizard", st)
25 if err != nil {
26 return err
27 }
28 c.SetCookie("signup_wizard", encoded, 1800, "/signup", "", true, true)
29 return nil
30}
31 
32func Step(c *gin.Context) {
33 st := loadState(c)
34 
35 if c.Request.Method == http.MethodPost {
36 switch st.Step {
37 case 0:
38 st.Email = c.PostForm("email")
39 case 1:
40 st.Company = c.PostForm("company")
41 case 2:
42 st.Plan = c.PostForm("plan")
43 }
44 st.Step++
45 
46 if st.Step >= 3 {
47 accounts.Create(st.Email, st.Company, st.Plan)
48 c.SetCookie("signup_wizard", "", -1, "/signup", "", true, true)
49 c.Redirect(http.StatusSeeOther, "/signup/done")
50 return
51 }
52 
53 if err := saveState(c, st); err != nil {
54 c.AbortWithStatus(http.StatusInternalServerError)
55 return
56 }
57 c.Redirect(http.StatusSeeOther, "/signup")
58 return
59 }
60 
61 c.HTML(http.StatusOK, fmt.Sprintf("signup/step%d.tmpl", st.Step), gin.H{
62 "state": st,
63 })
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Encrypted, signed cookies let you carry multi-step form state without any server-side session store.
  2. 2Advancing a step counter and redirecting after each POST implements the classic Post/Redirect/Get pattern.
  3. 3Clearing the cookie with a negative max-age is how you finalize and tear down transient wizard state.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A cookie-backed signup wizard in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code