{"id":1643,"date":"2026-09-22T22:42:46","date_gmt":"2026-09-23T01:42:46","guid":{"rendered":"https:\/\/www.linuxpro.com.br\/?p=1643"},"modified":"2026-09-22T22:53:21","modified_gmt":"2026-09-23T01:53:21","slug":"vue-go-echo-v5-parte-4-login-com-sessao-e-cookie","status":"publish","type":"post","link":"https:\/\/www.linuxpro.com.br\/en\/2026\/09\/vue-go-echo-v5-parte-4-login-com-sessao-e-cookie\/","title":{"rendered":"Vue.js + Go with Echo v5, part 4: login with session and cookie"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/vue-go-echo-v5-parte-4.webp\" alt=\"Mascote LinuxPro abrindo uma porta com cart\u00e3o de acesso e segurando um cookie, com tela de login do Vue e o cachorro caramelo cyborg sentado ao lado\" width=\"1486\" height=\"856\" \/><\/p>\n<p><strong>S\u00e9rie Vue.js + Go com Echo v5:<\/strong> <a href=\"\/2026\/09\/vue-go-echo-v5-parte-1-ambiente-e-primeira-api\/\">Parte 1: ambiente e primeira API<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-2-frontend-vue-com-vite\/\">Parte 2: frontend Vue com Vite<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-3-api-rest-de-tarefas\/\">Parte 3: API REST de tarefas<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-4-login-com-sessao-e-cookie\/\">Parte 4: login com sess\u00e3o e cookie<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-5-embed-binario-unico-e-docker\/\">Parte 5: embed, bin\u00e1rio \u00fanico e Docker<\/a><\/p>\n<p><strong>C\u00f3digo completo:<\/strong> todos os arquivos da s\u00e9rie est\u00e3o no <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08\">gist vue-go-echo-v5<\/a>, com um README que mostra em qual pasta cada um fica.<\/p>\n<p>A API de tarefas da <a href=\"\/2026\/09\/vue-go-echo-v5-parte-3-api-rest-de-tarefas\/\">parte 3<\/a> est\u00e1 aberta para qualquer um. Nesta parte ela ganha <strong>login<\/strong>: senha guardada como hash bcrypt, sess\u00e3o num <strong>cookie <code>HttpOnly<\/code><\/strong>, um <strong>middleware do Echo v5<\/strong> que bloqueia as rotas privadas e, no Vue, a tela de login com prote\u00e7\u00e3o de rotas no Vue Router. \u00c9 um desenho simples, mas com as escolhas certas de seguran\u00e7a para uma aplica\u00e7\u00e3o pequena.<\/p>\n<p><!-- more --><\/p>\n<h2>Por que sess\u00e3o em cookie, e n\u00e3o JWT no localStorage<\/h2>\n<p>Muitos tutoriais guardam um JWT no <code>localStorage<\/code> e o enviam em cada requisi\u00e7\u00e3o. Funciona, mas qualquer JavaScript da p\u00e1gina consegue ler esse token, e um XSS vira roubo de sess\u00e3o. Como aqui o frontend e a API est\u00e3o <strong>na mesma origem<\/strong> (proxy do Vite em desenvolvimento, bin\u00e1rio \u00fanico em produ\u00e7\u00e3o), d\u00e1 para usar o mecanismo mais antigo e mais seguro:<\/p>\n<ul>\n<li>o Go gera um token aleat\u00f3rio e o grava num cookie <code>HttpOnly<\/code>, que o JavaScript n\u00e3o consegue ler;<\/li>\n<li>o navegador envia o cookie sozinho a cada <code>fetch<\/code>;<\/li>\n<li><code>SameSite=Strict<\/code> impede que outro site dispare requisi\u00e7\u00f5es autenticadas em nome do usu\u00e1rio;<\/li>\n<li>o logout apaga a sess\u00e3o <strong>no servidor<\/strong>: o token deixa de valer na hora, o que um JWT sem lista de revoga\u00e7\u00e3o n\u00e3o faz.<\/li>\n<\/ul>\n<p>A aplica\u00e7\u00e3o tem um \u00fanico usu\u00e1rio, definido por vari\u00e1veis de ambiente. Com v\u00e1rios usu\u00e1rios, o que muda \u00e9 s\u00f3 de onde vem o hash da senha: de uma tabela no banco.<\/p>\n<h2>O backend de autentica\u00e7\u00e3o<\/h2>\n<p>Instale o pacote de bcrypt:<\/p>\n<pre><code class=\"language-bash\">go get golang.org\/x\/crypto\/bcrypt<\/code><\/pre>\n<p>Crie <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-auth-go\"><code>internal\/api\/auth.go<\/code><\/a>. Primeiro a estrutura e o construtor, que guarda s\u00f3 o hash da senha, nunca o texto:<\/p>\n<pre><code class=\"language-go\">package api\n\nimport (\n\t\"crypto\/rand\"\n\t\"crypto\/subtle\"\n\t\"encoding\/hex\"\n\t\"net\/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com\/labstack\/echo\/v5\"\n\t\"golang.org\/x\/crypto\/bcrypt\"\n)\n\nconst (\n\tcookieName = \"sessao\"\n\tsessionTTL = 8 * time.Hour\n)\n\n\/\/ Auth guarda o usu\u00e1rio \u00fanico da aplica\u00e7\u00e3o e as sess\u00f5es abertas.\ntype Auth struct {\n\tuser     string\n\tpassHash []byte\n\n\tmu       sync.Mutex\n\tsessions map[string]session\n}\n\ntype session struct {\n\tuser    string\n\texpires time.Time\n}\n\n\/\/ NewAuth recebe o usu\u00e1rio e a senha em texto e guarda s\u00f3 o hash bcrypt.\nfunc NewAuth(user, password string) (*Auth, error) {\n\thash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &amp;Auth{user: user, passHash: hash, sessions: map[string]session{}}, nil\n}\n\nfunc newToken() (string, error) {\n\tb := make([]byte, 32)\n\tif _, err := rand.Read(b); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.EncodeToString(b), nil\n}<\/code><\/pre>\n<p>O token de sess\u00e3o tem 32 bytes de <code>crypto\/rand<\/code>: imprevis\u00edvel, ao contr\u00e1rio de um contador ou de <code>math\/rand<\/code>.<\/p>\n<h3>Login<\/h3>\n<pre><code class=\"language-go\">type loginRequest struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\n\/\/ Login confere usu\u00e1rio e senha e cria a sess\u00e3o em um cookie HttpOnly.\nfunc (a *Auth) Login(c *echo.Context) error {\n\tvar req loginRequest\n\tif err := c.Bind(&amp;req); err != nil {\n\t\treturn echo.NewHTTPError(http.StatusBadRequest, \"JSON inv\u00e1lido\")\n\t}\n\tuserOK := subtle.ConstantTimeCompare([]byte(req.Username), []byte(a.user)) == 1\n\tpassOK := bcrypt.CompareHashAndPassword(a.passHash, []byte(req.Password)) == nil\n\tif !userOK || !passOK {\n\t\treturn echo.NewHTTPError(http.StatusUnauthorized, \"usu\u00e1rio ou senha inv\u00e1lidos\")\n\t}\n\n\ttoken, err := newToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.mu.Lock()\n\ta.sessions[token] = session{user: a.user, expires: time.Now().Add(sessionTTL)}\n\ta.mu.Unlock()\n\n\tc.SetCookie(&amp;http.Cookie{\n\t\tName:     cookieName,\n\t\tValue:    token,\n\t\tPath:     \"\/\",\n\t\tMaxAge:   int(sessionTTL.Seconds()),\n\t\tHttpOnly: true,\n\t\tSecure:   c.Request().TLS != nil || c.Request().Header.Get(\"X-Forwarded-Proto\") == \"https\",\n\t\tSameSite: http.SameSiteStrictMode,\n\t})\n\treturn c.JSON(http.StatusOK, map[string]string{\"username\": a.user})\n}<\/code><\/pre>\n<p>Pontos que valem a aten\u00e7\u00e3o:<\/p>\n<ul>\n<li>o bcrypt roda <strong>mesmo quando o usu\u00e1rio est\u00e1 errado<\/strong>, e a compara\u00e7\u00e3o do nome \u00e9 em tempo constante. Assim o tempo de resposta n\u00e3o revela se o usu\u00e1rio existe;<\/li>\n<li>a mensagem de erro \u00e9 a mesma para usu\u00e1rio e senha errados;<\/li>\n<li><code>Secure<\/code> liga quando a conex\u00e3o \u00e9 HTTPS, direta ou atr\u00e1s de um proxy que envia <code>X-Forwarded-Proto: https<\/code> (parte 5). Em <code>http:\/\/localhost<\/code> ele fica desligado, sen\u00e3o o navegador descartaria o cookie.<\/li>\n<\/ul>\n<h3>Logout, usu\u00e1rio atual e o middleware<\/h3>\n<pre><code class=\"language-go\">\/\/ Logout apaga a sess\u00e3o no servidor e o cookie no navegador.\nfunc (a *Auth) Logout(c *echo.Context) error {\n\tif ck, err := c.Cookie(cookieName); err == nil {\n\t\ta.mu.Lock()\n\t\tdelete(a.sessions, ck.Value)\n\t\ta.mu.Unlock()\n\t}\n\tc.SetCookie(&amp;http.Cookie{Name: cookieName, Value: \"\", Path: \"\/\", MaxAge: -1, HttpOnly: true})\n\treturn c.NoContent(http.StatusNoContent)\n}\n\n\/\/ Me devolve o usu\u00e1rio da sess\u00e3o atual.\nfunc (a *Auth) Me(c *echo.Context) error {\n\treturn c.JSON(http.StatusOK, map[string]any{\"username\": c.Get(\"user\")})\n}\n\n\/\/ Required \u00e9 o middleware que bloqueia as rotas sem sess\u00e3o v\u00e1lida.\nfunc (a *Auth) Required(next echo.HandlerFunc) echo.HandlerFunc {\n\treturn func(c *echo.Context) error {\n\t\tck, err := c.Cookie(cookieName)\n\t\tif err != nil {\n\t\t\treturn echo.NewHTTPError(http.StatusUnauthorized, \"fa\u00e7a login\")\n\t\t}\n\t\ta.mu.Lock()\n\t\ts, ok := a.sessions[ck.Value]\n\t\tif ok &amp;&amp; time.Now().After(s.expires) {\n\t\t\tdelete(a.sessions, ck.Value)\n\t\t\tok = false\n\t\t}\n\t\ta.mu.Unlock()\n\t\tif !ok {\n\t\t\treturn echo.NewHTTPError(http.StatusUnauthorized, \"sess\u00e3o expirada\")\n\t\t}\n\t\tc.Set(\"user\", s.user)\n\t\treturn next(c)\n\t}\n}<\/code><\/pre>\n<p>Um middleware no Echo \u00e9 uma fun\u00e7\u00e3o que recebe o pr\u00f3ximo handler e devolve outro. O <code>Required<\/code> barra a requisi\u00e7\u00e3o com 401 ou, se a sess\u00e3o for v\u00e1lida, guarda o usu\u00e1rio no contexto com <code>c.Set<\/code> e segue para o handler, que l\u00ea o valor com <code>c.Get<\/code>.<\/p>\n<h2>Proteja as rotas<\/h2>\n<p>Atualize <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-routes-go\"><code>internal\/api\/routes.go<\/code><\/a>. As rotas privadas recebem <code>auth.Required<\/code> como \u00faltimo argumento, o middleware no n\u00edvel da rota:<\/p>\n<pre><code class=\"language-go\">\/\/ Register liga as rotas em \/api.\nfunc Register(e *echo.Echo, auth *Auth, tasks *Tasks) {\n\tg := e.Group(\"\/api\")\n\n\t\/\/ Rotas p\u00fablicas\n\tg.GET(\"\/health\", func(c *echo.Context) error {\n\t\treturn c.JSON(http.StatusOK, map[string]string{\"status\": \"ok\"})\n\t})\n\tg.POST(\"\/login\", auth.Login)\n\tg.POST(\"\/logout\", auth.Logout)\n\n\t\/\/ Rotas que exigem sess\u00e3o: o middleware vai como \u00faltimo argumento\n\tg.GET(\"\/me\", auth.Me, auth.Required)\n\tg.GET(\"\/tasks\", tasks.List, auth.Required)\n\tg.POST(\"\/tasks\", tasks.Create, auth.Required)\n\tg.PATCH(\"\/tasks\/:id\", tasks.Update, auth.Required)\n\tg.DELETE(\"\/tasks\/:id\", tasks.Delete, auth.Required)\n}<\/code><\/pre>\n<p>Por que n\u00e3o um subgrupo <code>g.Group(\"\", auth.Required)<\/code>? No nosso teste, o subgrupo com prefixo vazio e middleware tamb\u00e9m capturava rotas inexistentes de <code>\/api<\/code>: uma URL errada respondia 401 em vez de 404. Com o middleware por rota, a rota inexistente continua dando 404 e a protegida d\u00e1 401, e a leitura fica expl\u00edcita.<\/p>\n<p>No <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-main-go\"><code>main.go<\/code><\/a>, a senha vem da vari\u00e1vel <code>APP_PASSWORD<\/code>, sem valor padr\u00e3o, e o servidor recusa subir sem ela:<\/p>\n<pre><code class=\"language-go\">func main() {\n\tuser := getenv(\"APP_USER\", \"admin\")\n\tpass := os.Getenv(\"APP_PASSWORD\")\n\tif pass == \"\" {\n\t\tslog.Error(\"defina APP_PASSWORD\")\n\t\tos.Exit(1)\n\t}\n\tauth, err := api.NewAuth(user, pass)\n\tif err != nil {\n\t\tslog.Error(\"hash da senha\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\te := echo.New()\n\te.Use(middleware.RequestLogger())\n\te.Use(middleware.Recover())\n\n\tapi.Register(e, auth, api.NewTasks())\n\n\tif err := e.Start(getenv(\"APP_ADDR\", \"127.0.0.1:8080\")); err != nil {\n\t\tslog.Error(\"servidor\", \"error\", err)\n\t}\n}\n\nfunc getenv(key, def string) string {\n\tif v := os.Getenv(key); v != \"\" {\n\t\treturn v\n\t}\n\treturn def\n}<\/code><\/pre>\n<p>Acrescente <code>\"os\"<\/code> aos imports. Teste o fluxo completo com <code>curl<\/code>, guardando o cookie em arquivo:<\/p>\n<pre><code class=\"language-bash\">APP_PASSWORD=segredo123 go run .<\/code><\/pre>\n<pre><code class=\"language-bash\">B=http:\/\/127.0.0.1:8080; J='Content-Type: application\/json'\ncurl -s -o \/dev\/null -w 'tasks sem login: %{http_code}\\n' $B\/api\/tasks\ncurl -s -H \"$J\" -d '{\"username\":\"admin\",\"password\":\"errada\"}' $B\/api\/login; echo\ncurl -s -c ck.txt -H \"$J\" -d '{\"username\":\"admin\",\"password\":\"segredo123\"}' $B\/api\/login; echo\ncurl -s -b ck.txt $B\/api\/me; echo\ncurl -s -b ck.txt -X POST -o \/dev\/null -w 'logout: %{http_code}\\n' $B\/api\/logout\ncurl -s -b ck.txt -o \/dev\/null -w 'me ap\u00f3s logout: %{http_code}\\n' $B\/api\/me<\/code><\/pre>\n<pre><code class=\"language-text\">tasks sem login: 401\n{\"message\":\"usu\u00e1rio ou senha inv\u00e1lidos\"}\n{\"username\":\"admin\"}\n{\"username\":\"admin\"}\nlogout: 204\nme ap\u00f3s logout: 401<\/code><\/pre>\n<p>A \u00faltima linha mostra a vantagem da sess\u00e3o no servidor: o mesmo cookie, reenviado depois do logout, j\u00e1 n\u00e3o vale nada.<\/p>\n<h2>A tela de login no Vue<\/h2>\n<p>Substitua <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-loginview-vue\"><code>web\/src\/views\/LoginView.vue<\/code><\/a>:<\/p>\n<pre><code class=\"language-html\">&lt;script setup&gt;\nimport { ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { api } from '..\/api'\n\nconst router = useRouter()\nconst route = useRoute()\nconst username = ref('admin')\nconst password = ref('')\nconst error = ref('')\nconst loading = ref(false)\n\nasync function login() {\n  error.value = ''\n  loading.value = true\n  try {\n    await api('POST', '\/login', { username: username.value, password: password.value })\n    router.push(route.query.next || '\/tarefas')\n  } catch (e) {\n    error.value = e.message\n  } finally {\n    loading.value = false\n  }\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;form class=\"card\" @submit.prevent=\"login\"&gt;\n    &lt;h1&gt;Entrar&lt;\/h1&gt;\n    &lt;label&gt;Usu\u00e1rio &lt;input v-model=\"username\" autocomplete=\"username\" required \/&gt;&lt;\/label&gt;\n    &lt;label&gt;Senha &lt;input v-model=\"password\" type=\"password\" autocomplete=\"current-password\" required \/&gt;&lt;\/label&gt;\n    &lt;p v-if=\"error\" class=\"error\"&gt;{{ error }}&lt;\/p&gt;\n    &lt;button :disabled=\"loading\"&gt;{{ loading ? 'Entrando...' : 'Entrar' }}&lt;\/button&gt;\n  &lt;\/form&gt;\n&lt;\/template&gt;<\/code><\/pre>\n<p>Nenhum token passa pelo JavaScript: o login s\u00f3 precisa saber se deu certo. Quem guarda a sess\u00e3o \u00e9 o navegador.<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/vue-go-echo-login.webp\" alt=\"Tela de login em Vue exibindo o erro de usu\u00e1rio ou senha inv\u00e1lidos devolvido pela API Go\" width=\"900\" height=\"560\" loading=\"lazy\" \/><\/p>\n<h2>Prote\u00e7\u00e3o de rotas no Vue Router<\/h2>\n<p>A guarda <code>beforeEach<\/code> que escrevemos na parte 2 agora funciona: ao abrir uma rota com <code>meta.auth<\/code>, ela chama <code>\/api\/me<\/code>; se receber 401, manda para <code>\/login?next=\/tarefas<\/code>, e depois do login o usu\u00e1rio volta para onde queria ir. Restaure o <code>meta: { auth: true }<\/code> e a linha do <code>\/me<\/code> no <code>TasksView<\/code>, se voc\u00ea as tirou para testar.<\/p>\n<p>Lembre que isso \u00e9 conveni\u00eancia de interface. <strong>Quem protege os dados \u00e9 o Go<\/strong>: mesmo que algu\u00e9m pule a guarda do Vue, cada rota da API continua exigindo a sess\u00e3o.<\/p>\n<p>Com <code>go run .<\/code> e <code>npm run dev<\/code> rodando, abra <code>http:\/\/localhost:5173\/tarefas<\/code>. No nosso teste com navegador automatizado, o fluxo inteiro passou:<\/p>\n<ul>\n<li>redirecionou para o login;<\/li>\n<li>recusou a senha errada;<\/li>\n<li>entrou;<\/li>\n<li>criou, marcou e removeu tarefas;<\/li>\n<li>manteve a sess\u00e3o ao recarregar a p\u00e1gina;<\/li>\n<li>depois do logout, voltou a exigir login.<\/li>\n<\/ul>\n<h2>O que falta para produ\u00e7\u00e3o<\/h2>\n<ul>\n<li><strong>Sess\u00f5es em mem\u00f3ria<\/strong> somem ao reiniciar e n\u00e3o funcionam com v\u00e1rias inst\u00e2ncias. Para isso, guarde-as num banco ou no Redis;<\/li>\n<li><strong>limite de tentativas:<\/strong> o Echo tem o middleware <code>RateLimiter<\/code>; aplique-o \u00e0 rota de login;<\/li>\n<li><strong>HTTPS<\/strong> \u00e9 obrigat\u00f3rio fora do <code>localhost<\/code>, sen\u00e3o o cookie trafega em texto claro. Na parte 5 colocamos o Nginx na frente.<\/li>\n<\/ul>\n<h2>Arquivos desta parte no gist<\/h2>\n<p>Cada arquivo abre direto no <a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08\">gist da s\u00e9rie<\/a>. O gist traz a <strong>vers\u00e3o final<\/strong> do projeto: <code>main.go<\/code> e <code>routes.go<\/code> ainda ganham login na parte 4 e o frontend embutido na parte 5.<\/p>\n<ul>\n<li><a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-auth-go\"><code>auth.go<\/code><\/a> (em <code>internal\/api\/auth.go<\/code>)<\/li>\n<li><a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-routes-go\"><code>routes.go<\/code><\/a> (em <code>internal\/api\/routes.go<\/code>)<\/li>\n<li><a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-main-go\"><code>main.go<\/code><\/a><\/li>\n<li><a href=\"https:\/\/gist.github.com\/jniltinho\/c95a0e7bbad4afe779cf143674cbef08#file-loginview-vue\"><code>LoginView.vue<\/code><\/a> (em <code>web\/src\/views\/LoginView.vue<\/code>)<\/li>\n<\/ul>\n<h2>Pr\u00f3ximo passo<\/h2>\n<p>A aplica\u00e7\u00e3o est\u00e1 completa, mas ainda roda como dois processos. Na <a href=\"\/2026\/09\/vue-go-echo-v5-parte-5-embed-binario-unico-e-docker\/\">parte 5<\/a> embutimos o Vue no bin\u00e1rio Go com <code>go:embed<\/code>, geramos um execut\u00e1vel \u00fanico, compilamos tudo com Docker e publicamos com systemd e Nginx.<\/p>\n<p><strong>S\u00e9rie Vue.js + Go com Echo v5:<\/strong> <a href=\"\/2026\/09\/vue-go-echo-v5-parte-1-ambiente-e-primeira-api\/\">Parte 1: ambiente e primeira API<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-2-frontend-vue-com-vite\/\">Parte 2: frontend Vue com Vite<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-3-api-rest-de-tarefas\/\">Parte 3: API REST de tarefas<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-4-login-com-sessao-e-cookie\/\">Parte 4: login com sess\u00e3o e cookie<\/a> \u00b7 <a href=\"\/2026\/09\/vue-go-echo-v5-parte-5-embed-binario-unico-e-docker\/\">Parte 5: embed, bin\u00e1rio \u00fanico e Docker<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Login with bcrypt, session in HttpOnly cookie, authentication middleware in Echo v5, and protected routes in Vue Router.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[45,2],"tags":[424,58,31,468],"class_list":["post-1643","post","type-post","status-publish","format-standard","hentry","category-desenv","category-linux","tag-api","tag-go","tag-golang","tag-vue"],"_links":{"self":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1643","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/comments?post=1643"}],"version-history":[{"count":2,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1643\/revisions"}],"predecessor-version":[{"id":1658,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/posts\/1643\/revisions\/1658"}],"wp:attachment":[{"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/media?parent=1643"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/categories?post=1643"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/en\/wp-json\/wp\/v2\/tags?post=1643"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}