Skip to content
This repository has been archived by the owner on Jan 30, 2019. It is now read-only.

Add API authentication mechanism #155

Merged
merged 10 commits into from
Aug 3, 2018
Merged

Add API authentication mechanism #155

merged 10 commits into from
Aug 3, 2018

Conversation

nickclyde
Copy link
Contributor

Fixes #133

I'm not crazy about having to pass the user metadata every time an API method is called from Rails, so I may try to figure out a better way to do that, but I'd like some initial feedback on the approach.

handler grpc.UnaryHandler,
) (interface{}, error) {
if md, ok := metadata.FromContext(ctx); ok {
if _, ok := md["skip_auth"]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think fundamentally, we can't let the client be the one deciding whether an RPC requires auth or not -- it would be too trivial for a malicious client to bypass auth on sensitive calls.

I think instead, each RPC implementation on the server-side should indicate somehow that it requires auth. Then the interceptor here would test to see if a given handler required auth, and then would call authorize.

Off the top of my head, I'm not sure what the best way is to do this. One challenge is that the methods have different signatures, so a naive approach of creating a type that defined them, and then mapping from that type to a bool (whether auth is required), would not be straightforward. You could do a map of interface{} to bool, type-switching on each method, but this could be very verbose. I think we need to think about how to do this before proceeding. There are probably a couple of different approaches. I am happy to help think through this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, that is a great point. I think I was just trying to get something working, but we definitely need to have the server deciding when to auth rather than the client. Let me see if I can get something working here, if I get stuck I'll reach out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are only two server methods that don't need to be authed, namely LoginUser and CreateUser, so if we can read the method names in the interceptor somehow, we can just skip for those methods. Not sure how to read which method is being called from there though, is it listed in the Context?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The info param to that function, of type UnaryServerInfo, is a struct type that has a field FullMethod which is the full RPC method string -- you could do some pattern-matching on that.

@kalilsn kalilsn dismissed paulsmith’s stale review August 2, 2018 21:10

Nick made the requested change (server decides which methods require auth)

@kalilsn kalilsn requested review from orenfromberg and robertfairhead and removed request for isawpalmetto August 2, 2018 21:10
Copy link
Contributor

@robertfairhead robertfairhead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Two thoughts on the server side. I have no input on the Rails stuff. I'm not qualified to say anything there.

}
key := []byte(os.Getenv("LOGIN_SECRET_KEY"))
h := hmac.New(sha512.New, key)
h.Write(userID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i understand this correctly, the user token is essentially static because Key + UserID should always result in the same token.

This isn't wonderful because it doesn't tie the authorization token to a specific session and it's not revocable. If somebody ever got the token value they could impersonate the user all day long.

Not an immediately critical thing to fix since some authorization is a step forward. But a thing to return to later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this doesn't seem great. Stealing this token would let you impersonate the user literally forever. I'll throw in a TODO.

Adding some randomness to the token isn't hard, but if we do that, we have to store it. So perhaps in the future we should put it in the database, tied to the user, along with a timestamp so it will expire automatically?

AFAIK soapboxd doesn't really have the concept of a session yet, so I'm very open to suggestions here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

The revocable bit is probably more important (since that's how most API tokens work for command-line etc.)

The session scoping would only make sense if we got rid of the CLI and went web-app only since then you could do an Oauth flow to get a JWT and present that.

Some authentication is better than none so may as well ship this and revisit.

soapboxd/user.go Outdated
@@ -91,19 +96,21 @@ func (s *server) LoginUser(ctx context.Context, req *pb.LoginUserRequest) (*pb.L
Error: genericLoginErrorMsg,
}

user, err := s.getUserByEmail(ctx, req.GetEmail())
if err == sql.ErrNoRows {
user, getUserErr := s.getUserByEmail(ctx, req.GetEmail())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm unclear why this doen't use err. It's not wrong, just not idiomatic to use anything other than err.

@kalilsn
Copy link
Contributor

kalilsn commented Aug 3, 2018

I tried to see if it was possible to eliminate the need for manually passing the user metadata with each request using an interceptor but couldn't figure out how to get the session data in my interceptor, so I'm just going to merge this. Might revisit that part later.

@kalilsn kalilsn merged commit 26d8dd4 into master Aug 3, 2018
@kalilsn kalilsn deleted the api-auth branch August 3, 2018 21:10
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants