diff --git a/cmd/example-app/main.go b/cmd/example-app/main.go
index ffa21c29ecb7a5dfcc1343850af3952690b1a048..3ec34e38bfcabe7fa9f8c2b0e851d4d8a4e20522 100644
--- a/cmd/example-app/main.go
+++ b/cmd/example-app/main.go
@@ -241,7 +241,7 @@ func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) {
 
 	authCodeURL := ""
 	scopes = append(scopes, "openid", "profile", "email")
-	if r.FormValue("offline_acecss") != "yes" {
+	if r.FormValue("offline_access") != "yes" {
 		authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState)
 	} else if a.offlineAsScope {
 		scopes = append(scopes, "offline_access")
@@ -254,34 +254,42 @@ func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) {
 }
 
 func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) {
-	if errMsg := r.FormValue("error"); errMsg != "" {
-		http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
-		return
-	}
-
-	if state := r.FormValue("state"); state != exampleAppState {
-		http.Error(w, fmt.Sprintf("expected state %q got %q", exampleAppState, state), http.StatusBadRequest)
-		return
-	}
-
-	code := r.FormValue("code")
-	refresh := r.FormValue("refresh_token")
 	var (
 		err   error
 		token *oauth2.Token
 	)
 	oauth2Config := a.oauth2Config(nil)
-	switch {
-	case code != "":
+	switch r.Method {
+	case "GET":
+		// Authorization redirect callback from OAuth2 auth flow.
+		if errMsg := r.FormValue("error"); errMsg != "" {
+			http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
+			return
+		}
+		code := r.FormValue("code")
+		if code == "" {
+			http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest)
+			return
+		}
+		if state := r.FormValue("state"); state != exampleAppState {
+			http.Error(w, fmt.Sprintf("expected state %q got %q", exampleAppState, state), http.StatusBadRequest)
+			return
+		}
 		token, err = oauth2Config.Exchange(a.ctx, code)
-	case refresh != "":
+	case "POST":
+		// Form request from frontend to refresh a token.
+		refresh := r.FormValue("refresh_token")
+		if refresh == "" {
+			http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest)
+			return
+		}
 		t := &oauth2.Token{
 			RefreshToken: refresh,
 			Expiry:       time.Now().Add(-time.Hour),
 		}
 		token, err = oauth2Config.TokenSource(r.Context(), t).Token()
 	default:
-		http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest)
+		http.Error(w, fmt.Sprintf("method not implemented: %s", r.Method), http.StatusBadRequest)
 		return
 	}
 
diff --git a/cmd/example-app/templates.go b/cmd/example-app/templates.go
index c0f9dfbd84d262990a3d05514b432e1039618588..a870d0f0a9d699d7dfef20fea0a71fea66c4e538 100644
--- a/cmd/example-app/templates.go
+++ b/cmd/example-app/templates.go
@@ -8,7 +8,7 @@ import (
 
 var indexTmpl = template.Must(template.New("index.html").Parse(`<html>
   <body>
-    <form action="/login">
+    <form action="/login" method="post">
        <p>
          Authenticate for:<input type="text" name="cross_client" placeholder="list of client-ids">
        </p>
@@ -50,8 +50,13 @@ pre {
   <body>
     <p> Token: <pre><code>{{ .IDToken }}</code></pre></p>
     <p> Claims: <pre><code>{{ .Claims }}</code></pre></p>
+	{{ if .RefreshToken }}
     <p> Refresh Token: <pre><code>{{ .RefreshToken }}</code></pre></p>
-    <p><a href="{{ .RedirectURL }}?refresh_token={{ .RefreshToken }}">Redeem refresh token</a><p>
+	<form action="{{ .RedirectURL }}" method="post">
+	  <input type="hidden" name="refresh_token" value="{{ .RefreshToken }}">
+	  <input type="submit" value="Redeem refresh token">
+    </form>
+	{{ end }}
   </body>
 </html>
 `))