{"id":955,"date":"2022-05-12T15:18:52","date_gmt":"2022-05-12T10:48:52","guid":{"rendered":"https:\/\/m-shaeri.ir\/blog\/?p=955"},"modified":"2023-03-13T09:17:12","modified_gmt":"2023-03-13T05:47:12","slug":"golang-login-manager-with-gomologin-package","status":"publish","type":"post","link":"https:\/\/mshaeri.com\/blog\/golang-login-manager-with-gomologin-package\/","title":{"rendered":"Golang login manager with Gomologin package"},"content":{"rendered":"\n<p id=\"017f\"> <\/p>\n\n\n\n<p><strong><a href=\"https:\/\/github.com\/birddevelper\/gomologin\">Gomologin<\/a><\/strong> is a Go package that handles all aspects of login process including user authentication, authorization, session management, and securing resources of our application behind login walls. It works with <strong>databases\/sql<\/strong> package to retrieve user information from SQL databases.<\/p>\n\n\n\n<p id=\"017f\">During the login process, the client\/browser sends a request containing the credential to server to check if it match the one in the database. If it matches, the user successfully logs in and Gomologin grant him\/her the access to web application\/website resources corresponding to its roles.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"e9e0\">Authentication and Authorization<\/h1>\n\n\n\n<p id=\"f51e\">Authentication is the process of identifying a user. In other words it determines who the users are. In other hand, Authorization checks wheather a user has the right to access a particular web resource or not.<\/p>\n\n\n\n<p><strong><a href=\"https:\/\/github.com\/birddevelper\/gomologin\">Gomologin<\/a><\/strong> is quite easy to use after the initial configuration. It can easily setup and customize login process of the application in couple of lines. It works with roles, so you can specify which roles has right to access which resources. Moreover, it has built-in session storage that allows developer to store data related to a user in a session. In the next sections I talk about all features of the Gomologin and teach you how to use it in your Go web application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to setup<\/h2>\n\n\n\n<p>Like every other Go package, we need to download it in our project using <strong>go get<\/strong> command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">go get github.com\/birddevelper\/gomologin<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>After downloading the package, we can import it in our code :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">package main  \n\nimport (      \n   \/\/ other packages\n\"github.com\/birddevelper\/gomologin\"\n   \n)<\/code><\/pre>\n\n\n\n<p>Then, we need to configure the Gomologin engine. However, most of parameters has default value and no need for explicit setting.<\/p>\n\n\n\n<p>Gomologin parameters are  :<\/p>\n\n\n\n<figure class=\"wp-block-table small_font\"><table><thead><tr><th>Parameter<\/th><th><strong>Function<\/strong><\/th><th><strong>Description<\/strong><\/th><th><strong>Example<\/strong><\/th><\/tr><\/thead><tbody><tr><td>LoginPage<\/td><td>SetLoginPage(path string)<\/td><td>Login page html template file path. Default path is&nbsp;<em><strong>.\/template\/login.html<\/strong><\/em><\/td><td>gomologin.Configure().SetLoginPage(&#8220;.\/templates\/myloginpage.html&#8221;)<\/td><\/tr><tr><td>LoginPath<\/td><td>SetLoginPath(path string)<\/td><td>Login url relative path. Default path is&nbsp;<em><strong>\/login<\/strong><\/em><\/td><td> gomologin.Configure().SetLoginPath(&#8220;\/&#8221;) <\/td><\/tr><tr><td>SessionTimeout<\/td><td>SetSessionTimeout(seconds int)<\/td><td>Number of seconds before the session expires. Default value is 120 seconds.<\/td><td>   gomologin.Configure().SetLoginPath(300) <\/td><\/tr><tr><td>PasswordEnceyption<\/td><td>SetPasswordEncryption()<\/td><td>A custom encryption function that accepts one argument in string and returns a string. Default function is <strong>EncNoEncrypt<\/strong> which do nothing on the given password.<strong>**<\/strong><\/td><td>    gomologin.Configure(). SetPasswordEncryption(gomologin.EncMD5)  <\/td><\/tr><tr><td>Sql query for authentication and retrieving roles <\/td><td>AuthenticateBySqlQuery()<\/td><td>SQL queries to check user credential and retrieve its roles by username and password sent from client. The authentication query must return only single arbitary column, it must have a where clause with two placeholder <strong>::username<\/strong> and <strong>::password<\/strong>. And the query for retrieving user&#8217;s roles must return only the text column containing the role name.<\/td><td>AuthenticateBySqlQuery(<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; db,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &#8220;select id from users where username = ::username and password = ::password&#8221;,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &#8220;select role from user_roles where userid = (select id from users where username = ::username)&#8221;)<\/td><\/tr><\/tbody><\/table><figcaption>Gomologin configuration parameters<\/figcaption><\/figure>\n\n\n\n<p>** You can set any function which accept  single  string and returns single string as encryption function. See the following example to understand the concept :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">\nfunc MyHashFuncion(s string) string {\n\n   return (\"fakeHash\"+s)\n\n}\n\n\nfunc main(){\n\n  \/\/ use MyHashFunction as password encryption method.\n  gomologin.Configure().SetPasswordEncryption(MyHashFunction)  \n\n\n\n}<\/code><\/pre>\n\n\n\n<p>Note that all of above listed functions return Config object which means we can call setting function sequentially one after another. See the example :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go line-numbers\">package main\n import (\n     \"database\/sql\"\n     \"fmt\"\n     \"log\"\n     \"net\/http\"\n     \"time\"\n     <span style=\"background-color: inherit; color: rgb(171, 178, 191); font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace; font-size: 0.9375rem;\">\"github.com\/birddevelper\/gomologin\"<\/span>\n<code>       _ \"github.com\/go-sql-driver\/mysql\"<\/code>\n )\n \n func main() {\n     \/\/ create connection to database\n     db, err := sql.Open(\"mysql\", \"root:12345@tcp(127.0.0.1:6666)\/mydb\")\n     if err != nil {\n         log.Fatal(err)\n     }\n     \/\/ Gomologin configuration \n    gomologin.Configure().\n    SetLoginPage(\".\/template\/login.html\"). \/\/ set login page html template path  \n    SetSessionTimeout(90).                 \/\/ set session expiration time in seconds \n    SetLoginPath(\"\/login\").                \/\/ set login http path \/\/ set database connection and sql query \n    AuthenticateBySqlQuery( db, \"select id from users where username = ::username and password = ::password\", \/\/ authentication query \n   \"select role from user_roles where userid = (select id from users where username = ::username)\") \/\/ fetch user's roles \n\n }<\/code><\/pre>\n\n\n\n<p>As you can see, the configuration took only 4 lines of code. After configuration done, we can wrap any handler function that we want to prevent from unauthenticated\/unauthorized access. See the example :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">    \/\/ instantiate http server \n    mux := http.NewServeMux() mux.Handle(\"\/static\/\", public())\n    \/\/ use Gomologin login handler for \/login endpoint \n    mux.Handle(\"\/login\", gomologin.LoginHandler())\n    \/\/ the pages\/endpoints that we need to protect should be wrapped with gomologin.LoginRequired \n    mux.Handle(\"\/mySecuredPage\", gomologin.LoginRequired(securedPage())) \n    \/\/ the pages\/endpoints those we want to be available for two roles =&gt; ADMIN and MODERATOR\n    mux.Handle(\"\/mySecuredPage2\", gomologin.RolesRequired(securedPage2()),\"ADMIN\",\"MODERATOR\") \n\n<\/code><\/pre>\n\n\n\n<p>You can see from the above example that I didn&#8217;t protect<strong> \/login<\/strong> path and  also <strong>\/public<\/strong> path in the web application which contains static files such as css, javascript and images.  <\/p>\n\n\n\n<p>The complete main.go file :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">\n\npackage main\n import (\n     \"database\/sql\"\n     \"fmt\"\n     \"log\"\n     \"net\/http\"\n     \"time\"\n     <span style=\"background-color: inherit; color: rgb(171, 178, 191); font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace; font-size: 0.9375rem;\">\"github.com\/birddevelper\/gomologin\"<\/span>\n<code>       _ \"github.com\/go-sql-driver\/mysql\"<\/code>\n )\n \/\/ static assets like CSS and JavaScript\n func public() http.Handler {\n     return http.StripPrefix(\"\/static\/\", http.FileServer(http.Dir(\".\/static\")))\n }\n \/\/ a page in our application, it needs user only be authenticated\n func securedPage() http.Handler {\n     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n         fmt.Fprintf(w, \"Hi! Welcome to secured page.\")\n     })\n }\n \/\/ another page in our application, it needs user be authenticated and have ADMIN role\n func securedPage2() http.Handler {\n     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n         fmt.Fprintf(w, \"Hi! Welcome to very secured page.\")\n     })\n }\n func main() {\n     \/\/ create connection to database\n     db, err := sql.Open(\"mysql\", \"root:12345@tcp(127.0.0.1:6666)\/mydb\")\n     if err != nil {\n         log.Fatal(err)\n     }\n     \/\/ Gomologin configuration \n    gomologin.Configure(). SetLoginPage(\".\/template\/login.html\"). \/\/ set login page html template path  \n    SetSessionTimeout(90).                 \/\/ set session expiration time in seconds \n    SetLoginPath(\"\/login\").                \/\/ set login http path \/\/ set database connection and sql query \n    AuthenticateBySqlQuery( db, \"select id from users where username = ::username and password = ::password\", \/\/ authentication query \n   \"select role from user_roles where userid = (select id from users where username = ::username)\") \/\/ fetch user's roles \n    \/\/ instantiate http server \n    mux := http.NewServeMux() mux.Handle(\"\/static\/\", public())\n    \/\/ use Gomologin login handler for \/login endpoint \n    mux.Handle(\"\/login\", gomologin.LoginHandler())\n    \/\/ the pages\/endpoints that we need to protect should be wrapped with gomologin.LoginRequired \n    mux.Handle(\"\/mySecuredPage\", gomologin.LoginRequired(securedPage())) \n    mux.Handle(\"\/mySecuredPage2\", gomologin.RolesRequired(securedPage2()),\"ADMIN\") \n    \/\/ server configuration \n    addr := \":8080\" \n    server := http.Server{ \n                    Addr:         addr,\n                    Handler:      mux,\n                    ReadTimeout:  15 * time.Second,\n                    WriteTimeout: 15 * time.Second,\n                    IdleTimeout:  15 * time.Second, \n              }\n    \/\/ start listening to network \n    if err := server.ListenAndServe(); err != nil {\n            log.Fatalf(\"main: couldn't start simple server: %v\\n\", err)\n    }\n }\n\n<\/code><\/pre>\n\n\n\n<p>In the whole application we can access the gomologin session storage as well as gomologin functions. <\/p>\n\n\n\n<figure class=\"wp-block-table aligncenter small_font\"><table><tbody><tr><td><span class=\"has-inline-color has-vivid-red-color\"><strong>Function<\/strong><\/span><\/td><td class=\"has-text-align-center\" data-align=\"center\"><span class=\"has-inline-color has-vivid-red-color\"><strong>Returns<\/strong><\/span><\/td><td><strong><span class=\"has-inline-color has-vivid-red-color\">Description<\/span><\/strong><\/td><td><\/td><\/tr><tr><td>gomologin.SetSession()<\/td><td class=\"has-text-align-center\" data-align=\"center\">&#8211;<\/td><td>This function stores a session key-value entry in session storage.<\/td><td><\/td><\/tr><tr><td>gomologin.GetSession()<\/td><td class=\"has-text-align-center\" data-align=\"center\">interface{}, bool<\/td><td>It retrieves session value by its key from session storage.<\/td><td><\/td><\/tr><tr><td>gomologin.RemoveSession()<\/td><td class=\"has-text-align-center\" data-align=\"center\">&#8211;<\/td><td>It removes a session entry by its key.<\/td><td><\/td><\/tr><tr><td>gomologin.GetCurrentUsername()<\/td><td class=\"has-text-align-center\" data-align=\"center\">string<\/td><td>It returns current authenticated username.<\/td><td><\/td><\/tr><tr><td>gomologin.GetDataReturnedByAuthQuery()<\/td><td class=\"has-text-align-center\" data-align=\"center\">interface{}<\/td><td>It returns the column which is specified in authentication select query.<\/td><td><\/td><\/tr><tr><td>gomologin.GetCurrentUserRoles()<\/td><td class=\"has-text-align-center\" data-align=\"center\">[]string<\/td><td>Returns current authenticated user roles in string slice.<\/td><td><\/td><\/tr><tr><td>gomologin.HasRole(role)<\/td><td class=\"has-text-align-center\" data-align=\"center\">bool<\/td><td>It checks wheather the user poses the given role or not.<\/td><td><\/td><\/tr><tr><td>gomologin.LoginRequired()<\/td><td class=\"has-text-align-center\" data-align=\"center\">http.Handler<\/td><td>It&#8217;s a wrapper function, it accepts Handler and procced the request only if the client is already authenticated.<\/td><td><\/td><\/tr><tr><td>gomologin.RolesRequired()<\/td><td class=\"has-text-align-center\" data-align=\"center\">http.Handler<\/td><td>It&#8217;s a wrapper function, it accepts Handler and procced the request only if the client is already authenticated and it poses the specified roles.<\/td><td><\/td><\/tr><tr><td>gomologin.LoginHandler()<\/td><td class=\"has-text-align-center\" data-align=\"center\">http.Handler<\/td><td>It returns Handler of login page. It should be routed to your login url path.<\/td><td>mux.Handle(&#8220;\/login&#8221;, gologin.LoginHandler())<\/td><\/tr><\/tbody><\/table><figcaption>Gomologin package functions<\/figcaption><\/figure>\n\n\n\n<p> <\/p>\n\n\n\n<p>Code examples showing how to use gomologin functions in project :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">\/\/ a page in our application\nfunc securedPage() http.Handler {\n&nbsp; &nbsp; return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n&nbsp; &nbsp; &nbsp; &nbsp; id := gomologin.GetDataReturnedByAuthQuery(r)\n        \/\/ get current authenticated username\n&nbsp; &nbsp; &nbsp; &nbsp; username := gomologin.GetCurrentUsername(r)\n        \/\/ create a new session data\n&nbsp; &nbsp; &nbsp; &nbsp; gomologin.SetSession(\"test\", \"hello\", r)\n        \/\/ get the already stored data back. \n&nbsp; &nbsp; &nbsp; &nbsp; txt, _ := gomologin.GetSession(\"test\", r)\n        \n        \/\/ As the GetSession return type is interface{}, we should specify the exact type of the session data\n&nbsp; &nbsp; &nbsp; &nbsp; fmt.Fprintf(w, txt.(string)+\" Hi \"+username+\"! Welcome to secured page. Your Id is \"+strconv.FormatInt(id.(int64), 10)+\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \" &lt;a href='\/login?logout=yes' &gt; Logout &lt;\/a&gt;\")\n        \/\/ GetCurrentUserRoles return slice of string containing current user roles\n&nbsp; &nbsp; &nbsp; &nbsp; for _, role := range gomologin.GetCurrentUserRoles(r) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Fprintf(w, \" Role 1 : \"+role+\"\\n\")\n&nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; })\n}<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>At last but not the least, you will be in need of logout action. Gomologin already has solution for it. Just send user to <strong>\/yourloginpath?logout=yes<\/strong>. It clears all session data and log out the user.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Gomologin is a Go package that handles all aspects of login process including user authentication, authorization, session management, and securing resources of our application behind &hellip; <\/p>\n","protected":false},"author":1,"featured_media":1033,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[141,144,1,139,142,140,35,143],"tags":[132,134,131,126,127,128,129,136,135,130,137,138],"_links":{"self":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/955"}],"collection":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/comments?post=955"}],"version-history":[{"count":0,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/955\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media\/1033"}],"wp:attachment":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media?parent=955"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/categories?post=955"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/tags?post=955"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}