<p>Hey Reddit,
I am writing Auth into my server atm and I was hoping for some critiques or advice as well as answers to a few questions.
Thanks :)</p>
<p>Here is my approach.</p>
<h2></h2>
<p><strong>REGISTRATION</strong></p>
<p><em>A.</em> User sends</p>
<ul>
<li>Email</li>
<li>Username</li>
<li>Hashed Password</li>
</ul>
<p><em>B.</em> Server checks that either the <strong>Email</strong> or <strong>Username</strong> doesn't already exist(whatever I decide is the human readable unique identifier). It then generates some <strong>Salt</strong> and adds that to the end of the <strong>Hashed Password</strong>. The server then hashes the two and creates the record</p>
<ul>
<li>ID</li>
<li>Email</li>
<li>Username</li>
<li>Hashed Password</li>
<li>Salt</li>
</ul>
<p><em>C.</em> Server proceeds to perform the login function (but skips Auth in Login)</p>
<h2></h2>
<p><strong>LOGIN</strong></p>
<p><em>A.</em> User sends</p>
<ul>
<li>Unique Identifier</li>
<li>Hashed Password</li>
</ul>
<p><em>B.</em> Server takes the <strong>Unique Identifier</strong> and looks up the user in the database. If the user exists, it takes the users <strong>Salt</strong> and appends it to the <strong>Hashed Password</strong>. It hashes this and then checks this against the stored <strong>Password</strong>. If its a match, the server proceeds</p>
<p><em>C.</em> Server now generates a long series of random characters I will call the <strong>Session Token</strong>(proper name?), it then stores that in a database along with the users <strong>ID</strong> and any other information I would frequently lookup that is non-dynamic.</p>
<p><em>D.</em> The server then returns the <strong>Session Token</strong> to the client</p>
<p><em>E.</em> If the user requests to logout, destroy the row in the database</p>
<h2></h2>
<p><strong>REQUESTS</strong></p>
<p><em>A.</em> Whenever a user sends a request, it puts the <strong>Session Token</strong> in the Header</p>
<p><em>B.</em> The server reads the <strong>Session Token</strong> from the header and checks it against its <strong>Session Token</strong> database. This reliably proves who the user is</p>
<h2></h2>
<p><strong>QUESTIONS:</strong></p>
<ul>
<li>1. During login, should I check to see if this user already exists in the Session Token table and return that Session Token</li>
<li>2. Should I put a expiration date on the Session Tokens? If so how long do you suggest</li>
<li>3. The length of the salt doesn't matter that much correct?</li>
</ul>
<p><strong>MORE QUESTIONS:</strong></p>
<ul>
<li>1. To speed up the operation I should put the Session Database into RAM somehow right? If so can you give me any advice on properly doing that.</li>
</ul>
<hr/>**评论:**<br/><br/>elithrar_: <pre><p>I'm going to try and answer this as succinctly as possible:</p>
<h2>Registration</h2>
<ol>
<li>User sends email, username, password to your server (tip: just use the email address as the username if you can)</li>
<li>Check for uniqueness</li>
<li>Generate a password using Go's bcrypt lib <a href="https://godoc.org/golang.org/x/crypto/bcrypt">https://godoc.org/golang.org/x/crypto/bcrypt</a> or my scrypt lib <a href="https://github.com/elithrar/simple-scrypt">https://github.com/elithrar/simple-scrypt</a></li>
<li>Create a session cookie using <a href="http://www.gorillatoolkit.org/pkg/sessions">http://www.gorillatoolkit.org/pkg/sessions</a> - storing the user ID (i.e. the UUID from the database) is good here. Provided the cookies are authenticated, and you're using a lib that checks, you can validate users on the presence of a <code>User</code> object or ID in the session cookie. Set a reasonable expiry based on your application - no longer than a week, IMO. Less is better.</li>
</ol>
<h2>Login</h2>
<ol>
<li>Accept email/password</li>
<li>Look up user based on email, retrieving their password hash at the same time (usually you just retrieve your <code>User</code> object with those as fields)</li>
<li>Use <code>bcrypt.CompareHashAndPassword</code> to compare their password from the POST form with the one you stored upon registration.</li>
<li>Create a session cookie using <a href="http://www.gorillatoolkit.org/pkg/sessions">http://www.gorillatoolkit.org/pkg/sessions</a> - storing the user ID (i.e. the UUID from the database) is good here. Provided the cookies are authenticated, and you're using a lib that checks, you can validate users on the presence of a <code>User</code> object or ID in the session cookie. Set a reasonable expiry based on your application - no longer than a week, IMO. Less is better.</li>
</ol>
<h2>Requests</h2>
<ol>
<li><p>Write some middleware (see below for an outline) and wrap your routes or sub-router with it. e.g.</p>
<pre><code> func CheckAuth(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "session_name")
if err != nil {
// Just a basic error page for this example
http.Error(w, http.StatusText(http.InternalServerError), http.InternalServerError)
return
}
// Check for a user object in session.Values
// Re-direct to /login or throw a HTTP 403 if it doesn't exist
// Check that (e.g.) user.ID is not empty
// Call the next handler via h.ServeHTTP(w, r)
// Else bounce them back to the login URL / throw a 403 again.
}
</code></pre></li>
<li><p>Within this middleware, get the session, check if a non-nil <code>User</code> object is stored in there, check it hasn't expired (gorilla/sessions does a lot of this for you) and then call the next handler. Otherwise, re-direct to your <code>/login</code> route or call a <code>HTTP 403 Forbidden</code>(your preference).</p></li>
</ol>
<h3>Other notes:</h3>
<ul>
<li>Just use session cookies via <a href="http://www.gorillatoolkit.org/pkg/sessions">gorilla/sessions</a>. They are sent on every request (for which the path applies, to be precise) - they are already "in RAM" by the time your handlers are doing anything. You don't need to use a database as a session store for authentication. If you did, it still uses cookies to identify which database row holds the data.</li>
<li>The length of the salt does matter, but if you use the <code>bcrypt</code> or <code>scrypt</code> libs I mentioned above, they handle that for you.</li>
<li>See my other comments about expiration dates. Keep 'em as short as your users can manage. Write a <code>Logout</code> handler that sets the MaxAge field of the cookie in the past to "delete" the cookie for users who want to logout (the gorilla/sessions docs cover this)</li>
</ul></pre>devsquid: <pre><p>Awesome. Thank you. I should mention this API is being used for a native app as well as a website. I am already using Gorilla for routing and I will look into sessions.</p>
<p>So storing a session in RAM alone is completely fine? Maybe storing a receipt of it in the DB isn't a bad idea as well anyways. Not that it matters in my case, but would that solution be very scaleable? I guess if scaling was an issue you would have the server put the Session in a shared cache right?</p></pre>elithrar_: <pre><p>You don't store the session on the server - you store it in HTTP cookies.</p>
<p>There's no need to store a ticket or receipt, as the cookie will be authenticated at a minimum (if you use gorilla/sessions correctly). This means that only your application can modify it correctly, so you can "trust" what the cookie contains. </p>
<p>The <code>User{ID, Username, Email, PasswordHash}</code> is stored in the database upon registration and then consulted during login only. </p></pre>devsquid: <pre><p>Ok but the HTTP Cookie is stored in ram, right?</p></pre>elithrar_: <pre><p>Cookies are stored in the client (user) browser. Your server sets a <code>Set-Cookie</code> header with the cookie data encoded as a string. </p>
<p>The client's browser sends that cookie back on every request via a <code>Cookie</code> header, which your application verifies (via HMAC; gorilla/sessions again!) and then inspects for the data you want (a user ID, for example). If you are thinking this is "slow" for some reason, it's not: the cookie contents end up in your web server's memory as part of the HTTP request. Cookies do impart a small overhead (up to 4KB, typically) on the data transferred but it's typically far less. Consulting a database is significantly slower. </p>
<p>Cookies are effectively "stateless" - you don't need to save any information about the cookie on your side. The cookie itself stores that data. </p></pre>devsquid: <pre><p>Huh interesting. Thanks for pointing me in this direction. This is a much better solution, than my Tokens in RAM idea.</p></pre>devsquid: <pre><p>No i wasn't thinking it was slow, I was thinking my original idea was slow.... lol</p></pre>devsquid: <pre><p>Awesome man, implementation of Gorilla's Cookie library was eassssy af</p></pre>devsquid: <pre><p>So would storing a session in just a Map or a standard Array be advisable?</p></pre>: <pre><p>[deleted]</p></pre>devsquid: <pre><p>Why? I shouldn't add salt before hashing?</p></pre>: <pre><p>[deleted]</p></pre>devsquid: <pre><p>Huh I still don't get how does a Salt not protect a database. It means that someone ever got your database, they would have to decrypt each of the users passwords individually. You couldn't use a single lookup table across the entire database.</p></pre>anacrolix: <pre><p>Use bcrypt.</p></pre>devsquid: <pre><p>Yes I get the benefit of Hashing that can't be as optimized. But you are saying I shouldn't use Salt and I am curious why you think that.</p></pre>teepark: <pre><p>bcrypt includes a salt automatically. it's a widely cryptographer-approved complete password management package, that's the reason for the cargo-cultish chant.</p></pre>devsquid: <pre><p>Cool how does it include a salt automatically? How does it reliably get the Salt?</p></pre>teepark: <pre><p>the hash it spits out has the salt as a prefix. when generating a hash it also recognizes when you use a bcrypt-generated hash as the salt and just pulls the prefix.</p>
<p>thus checking a password is actually just generating a hash of the plaintext password using the hash you're checking against as the salt, and seeing if you get the same thing out. the go package wraps this up in a reasonable api (also with a constant-time compare at the end) though; I only know this because I've done it in python.</p>
<p>this is also compatible with the variable work factor, because it actually encodes the work factor into the salt.</p></pre>devsquid: <pre><p>Ok so it stores the salt with the hashed password. Cool thank you for answering my question and now just repeating dogma :)</p></pre>nindalf: <pre><p>Check out these two articles by Matasano Security on which password scheme you should use - <a href="http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-secure-password-schemes.html" rel="nofollow">from 2007</a> and from earlier <a href="http://chargen.matasano.com/chargen/2015/3/26/enough-with-the-salts-updates-on-secure-password-schemes.html" rel="nofollow">this year</a>.</p>
<p>Both are excellent articles and I feel that all developers should listen to what they say.</p></pre>devsquid: <pre><p>Its also not like SHA256 is insecure in anyway, its just a easily optimized. A Salted and hashed password would still take forever to decrypt. But I will use bcrypt</p></pre>elithrar_: <pre><blockquote>
<p>Its also not like SHA256 is insecure in anyway, its just a easily optimized</p>
</blockquote>
<p>Brute forcing SHA-256 (a hash function) vs bcrypt, scrypt or PBKDF2 with the appropriate work factor (all of which are key derivation functions) is a MAGNITUDE of order faster. The SHA family is not designed for long-term key storage.</p>
<p>As a shameless plug: <a href="https://github.com/elithrar/simple-scrypt" rel="nofollow">https://github.com/elithrar/simple-scrypt</a> else use <a href="https://godoc.org/golang.org/x/crypto/bcrypt" rel="nofollow">https://godoc.org/golang.org/x/crypto/bcrypt</a></p></pre>devsquid: <pre><p>Yes, i get its a huge order of magnitude more of work and can not be as easily hardware optimized.</p></pre>hahainternet: <pre><p>What you don't understand is that a simple salt is trivially defeated. Smart mechanisms like crypt add the salt to the password and then repeatedly hash many hundreds of times using this salt. This way a brute force has to redo each hash with the discovered salt for each potential key and each potential passphrase.</p>
<p>Bcrypt uses a similar approach but with a configurable amount of re-hashing which allows you to make it hard in terms of CPU time.</p>
<p>If you need to learn more about this, PM me or ask nicely in one of the security subreddits.</p></pre>devsquid: <pre><p>I did ask nicely but the only answer i got was use bcrypt which is good advice but was in no way an answer to the question.</p>
<p>Also by trivial you mean very expensive and several decades for a single password.</p></pre>hahainternet: <pre><blockquote>
<p>Also by trivial you mean very expensive and several decades for a single password.</p>
</blockquote>
<p>Nope. Few GPUs and a salt and hash is easily defeated.</p></pre>devsquid: <pre><p>Really even when properly salted hash with like sha512?</p>
<p>But even bcrypt salts the input, the issue isn't salting it's weak algorithms.</p></pre>gogolang: <pre><p>I wish there were an open-source Go library for user auth that included registration, password resets via e-mail, and all the hashing and session tokens. Getting security right is tricky and I'm betting that most people who are doing it on their own are going to get some part of it wrong. Perhaps yours could be that library.</p>
<p>Your approach looks reasonable, but it also kind of depends how much security you want. First, I would not delete the session tokens on logout. Instead, I would keep the session tokens in the database and just flag that this session is logged out. I would also store the IP address alongside each session token generated. That way, you can give the user a way to examine past logins to search for any suspicious activity in their login history. </p>
<p>Expiring your session tokens is really dependent on the application. If it's a banking application, it should be something like 30 minutes to an hour with a heartbeat that resets the clock when there's user activity. </p>
<p>The salt should be long too. If your database is compromised and an attacker wants to uncover the password of a single individual, having a short salt will make it easier to uncover the password especially if the password is weak. </p></pre>klaaax: <pre><p>You could have that,but then some idiot would write a blog post about "why you shouldn't use library X" and shame the maintainer publicly.</p></pre>devsquid: <pre><p>So true, but you gotta just let the angry bloggers blog.</p></pre>gogolang: <pre><p>It's ok. We're all Go developers here. If you've spent any time on Quora you'll read a ton of Go bashing from the very vocal minority of Haskell programmers about why you shouldn't use Go and instead use Haskell. </p></pre>elithrar_: <pre><blockquote>
<p>I wish there were an open-source Go library for user auth that included registration, password resets via e-mail, and all the hashing and session tokens. Getting security right is tricky and I'm betting that most people who are doing it on their own are going to get some part of it wrong. Perhaps yours could be that library.</p>
</blockquote>
<p>The hard part is that doing so requires the library author to make a <em>ton</em> of assumptions about the underlying application.</p>
<p>It's "easy" to write a lib like that for (say) Rails, Django, Flask because you can assume a lot of abstraction. ActiveRecord, for example. Go, on the other hand, is a language (and not a framework!) and users typically prefer to glue smaller libraries together instead of using 'kitchen sink' frameworks.</p>
<p>Writing a complete authentication library with registration logic, user privs, password resets, etc means you need to either force an ORM onto your package users or tie yourself to a specific datastore (i.e. Postgres) because you need to store a lot of that data (users, password hashes, reset links) <em>somewhere</em>.</p>
<p>I'm not saying it's impossible, but it's certainly a lot of work to abstract an authentication system to that extent, and you'd end up serving a very specific niche.</p></pre>devsquid: <pre><p>Thats a good point. I imagine once the ecosystem and the language matures, something will arise. But I still want to make my own right now anyways. I am not doing this for a client, its just for myself.</p></pre>: <pre><p>[deleted]</p></pre>deiwin: <pre><p>This <a href="https://github.com/go-authboss/authboss">https://github.com/go-authboss/authboss</a> ?</p></pre>devsquid: <pre><p>It doesn't seem that hard to do correctly. I like tracking the sessions rather then deleting them.</p></pre>devsquid: <pre><p>Why does the length of the salt matter, if its sufficiently long such that there are no repeats?</p></pre>boxtown: <pre><p>Hashing on the client side doesn't really add anything. You should be using HTTPS for any request containing information like passwords which obviates the need for obfuscation by hashing. </p>
<p>Expiration dates are definitely a good idea but the amount of time is up to you. You have to balance between being safe but not annoying (e.g. a 20 min TTL)</p>
<p>Again, its up to you but I would probably generate a new token per login request. If you use the same token, then a user logging out will log him out of any session he may have open</p></pre>devsquid: <pre><p>I figured hashing on the client side, incase there was any form of MITM attack.</p></pre>teepark: <pre><p>client-side hashing is a false security. not directly harmful, but not that helpful either.</p>
<p>the indirect harm would be if you thought that because you're doing client-side hashing, you don't have to force HTTPS-only with a valid signed cert on (at least) your registration endpoint. that's the real way to protect against MITM scenarios.</p></pre>elithrar_: <pre><blockquote>
<p>I figured hashing on the client side, incase there was any form of MITM attack.</p>
</blockquote>
<p>If you use TLS (HTTPS) this isn't a real problem.</p>
<p>If you don't, and you enact client-side hashing, you're still sending the JavaScript that does the hashing over the insecure channel. Someone could trivially MitM your JavaScript hash function, and then MitM the cleartext password over the wire anyway. Zero gains. If they can MitM your connection, MitM'ing your JavaScript is <em>trivial</em> by that point.</p></pre>boxtown: <pre><p>If you're using HTTPS, then there's really no worry of a MITM attack.
If you're not, then hashing won't really help you either because the MITM could just capture the hashed password and it wouldn't be any different than him capturing the unhashed password.</p></pre>koffiezet: <pre><p>Personally, I dislike passwords, and prefer something like this:</p>
<ul>
<li>user enters email
<ul>
<li>if user doesn't exist: create new</li>
</ul></li>
<li>generate random string, and insert in DB</li>
<li>send user an email containing a login link containing the random string</li>
</ul>
<p>The user then clicks the link:</p>
<ul>
<li>You validate the email address + random string combination</li>
<li>store login in session in cookie</li>
</ul>
<p>Done.</p></pre>devsquid: <pre><p>That sorta works like Steve Gibson's sqrl technique. But still I don't think that method provides a good ux. It would seem like it would be better to just use oauth at that point. Thanks for your input though</p></pre>bmo111: <pre><p>Take a look at this. It has the basic use of bcrypt with user authentication. <a href="https://github.com/vegax87/Negroni-Boilerplate" rel="nofollow">https://github.com/vegax87/Negroni-Boilerplate</a></p>
<p>Feel free to ping me if you have any questions. I wrote something similar but sends a one time use token for registration to the user's email for them to click on. Once they have clicked that link, they can proceed to use the system as a user. This is to avoid bots from signing up.</p></pre>ikofai: <pre><p>Hello! As mentioned elsewhere in this thread there is: <a href="https://github.com/go-authboss/authboss" rel="nofollow">https://github.com/go-authboss/authboss</a> - It has a pretty comprehensive usage guide in the readme.</p>
<p>I'm actually one of the owners of project and I suggest you check it out as it achieves what you're trying to do and more. Even if you don't use it, it may be a good learning source for you to reference.</p>
<p>Additionally, we have a sample blog that utilizes it (as well as gorilla mux, gorilla session, etc): <a href="https://github.com/go-authboss/authboss-sample" rel="nofollow">https://github.com/go-authboss/authboss-sample</a></p>
<p>Feel free to PM me, post an issue on Github, or talk to us on Gitter: <a href="https://gitter.im/go-authboss/authboss" rel="nofollow">https://gitter.im/go-authboss/authboss</a></p>
<p>We're continually adding features as our spare time allows and are always looking for feedback.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传