Handling OAuth Responses & Sessions - session

At the end of an OAuth2 token exchange, I'm [typically] left with a JSON array of user data that I've un-marshalled into a struct (say, GoogleUser) with the fields I care about.
What is the sensible way of recording that data to my DB? Just call a CreateUser function from the callback handler, pass the struct and save it (the obvious way to me), after checking that the user doesn't already exist in the DB?
I assume I should then create a session token (i.e. session.Values["authenticated"] == true) in the callback handler, store that in a cookie (with a reasonable expiry date) and simply just check for if authenticated == true on any handler functions that expect a logged-in user? Or, for admin handlers: if admin_user == true. What are the risks here (if any) presuming I'm talking over HTTPS and using secure cookies?
Apologies for the basic questions: just trying to get a grip on "best practice" ways to log users in w/ OAuth.

With regards to your first question, It's usually recommended to do the check and insert in a single transaction. It depends on what DB you're using, but these are usually referred to as UPSERT statements. In PLSQL it looks a bit like this (modify to taste):
CREATE FUNCTION upsert_user(emailv character varying, saltv character varying, hashv character varying, date_createdv timestamp without time zone) RETURNS void
LANGUAGE plpgsql
AS $$;
BEGIN
LOOP
-- first try to update the key
UPDATE users SET (salt, hash) = (saltv, hashv) WHERE email = emailv;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO users(email, salt, hash, date_created) VALUES (emailv, saltv, hashv, date_createdv);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$$;
In regards to your second question, usually Secure cookies over HTTPS is enough. I'd set the HttpOnly option, and usually the Path option as well.
HttpOnly means that the cookie can't be accessed by JS (only HTTP or HTTPS), and the Path option allows you to specify what path (in the URL) the cookie is valid for.

The Access Token in OAuth standard have a expiry. It's usually determined by authorization server. In your case I assume you are on authorization server side.
Read RFC 6750 for example:
Typically, a bearer token is returned to the client as part of anOAuth 2.0 [RFC6749] access token response. An example of such a response is:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"mF_9.B5f-4.1JqM",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
Also read concept of Access Token in RFC 6749:
The access token provides an abstraction layer, replacing different
authorization constructs (e.g., username and password) with a single
token understood by the resource server. This abstraction enables
issuing access tokens more restrictive than the authorization grant
used to obtain them, as well as removing the resource server's need
to understand a wide range of authentication methods.
So in your case, I don't think a "cookie" or "admin handler" is needed. You only have to generate Access Token & Refresh Token for each users logged in just like OAuth spec says, and store its expiry as well. You can also provide a hash method related with Access Token to make sure it's a legal request. For example, users use their access token to generate a signature with hash & salt method, send access token & signature to server to verify. Read Public Key Encryption for more details.
Furthermore, you don't need to save these tokens into your DB because they are all temporary resources. You can also save all user informations in memory and implement a cache layer to save these informations which truly important into DB periodically(which I'm currently using now) to lower DB pressure.

Related

Slack - How to verify interactivity requests

Setting up my first Slack slash command. I built it out originally using the deprecated verification token but, for posterity, have decided to use signed secrets authentication.
Reading through the signed secrets documentation, I've had no issue validating requests that come in from the initial slash command. However, interaction requests have a completely different body structure and the method for calculating a secret hash do not produce a valid result (because the request body is different).
Here is a snippet from the docs on validating signed secrets.
slack_signing_secret = 'MY_SLACK_SIGNING_SECRET' // Set this as an environment variable
>>> 8f742231b10e8888abcd99yyyzzz85a5
request_body = request.body()
>>> token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c
On invocation of the slash command this works as intended - the request body matches the structure in the example above. When the user interacts with the message, the response body uses the blocks api - which is completely different
If I'm not supposed to use the verification token and the request body from the interactive blocks api does not allow me to compute a valid hash, how am I supposed to validate interaction requests? I must be missing something while combing through the docs.

How does validation of JWT distinguish difference between token types?

I am playing around with building a custom Oauth2/OpenID library, and is having thoughts about validating the tokens.
I am using only JWT for all token types (Access, Refresh & ID), but I am thinking; How would the resource server validate ex. the access token, and make sure it is only access tokens from the issuer being accepted, and not refresh or ID tokens, since they would also be valid, hence they come from the same trusted issuer?
Likewise, how would make sure, the token sent with a refresh grant, is not just an valid access token, since it would also be verified...
I know an easy fix would be just making a custom claim, describing what kind of token it is, or using different signatures for each, but is there a "right" way of doing it?
One way to separate the ID token from the Access token is by looking at the typ claim in the JWT-header.
Some IdentityProviders uses the at+jwt typ to indicate that the token is an access token that follows certain rules. But this is not a mandatory thing to follow.
{
"typ":"at+JWT",
"alg":"RS256",
"kid":"RjEwOwOA"
}
Otherwise they can look at the claims inside the token to determine if it is an access or ID-token. The token-signature itself can't be used to determine the token type.
You can read more about the standard for access token here and here
Refresh and reference tokens are typically not in a JWT format, instead they are more like a random string.

Deleting all cookies

I'm having a difficult time deleting all cookies using the following code. What seems to happen is that the domain is changed to append a dot in front of it. Instead of deleting all cookies, I get duplicate cookies with slightly different domains. Is there any way to completely remove all cookies no matter what their domain looks like?
Thanks for any help!
//DeleteCookies deletes all cookies
func DeleteCookies(w http.ResponseWriter, r *http.Request) {
for _, c := range r.Cookies() {
deleted := &http.Cookie{
Name: c.Name,
Path: c.Path,
//Expires: time.Unix(0, 0),
MaxAge: -10,
HttpOnly: c.HttpOnly,
Domain: c.Domain,
Secure: c.Secure,
Value: "",
}
http.SetCookie(w, deleted)
}
}
What you try to do doesn't work well as cookies do not work that way.
The easy things first:
HttpOnly, Domain and Secure: These values are not transmitted in a HTTP client request, these fields of c will always be empty. The client uses these fields to determine whether to send a (Name,Value)-pair in the Cookie header or not but doesn't send these values.
For HttpOnly, Secure (and SameSite) this does not matter as these (and MaxAge and Expires) do not contribute to the cookie identity.
Cookie identity is based on the tripple (Domain,Path,Name) as sent in a SetCookie header. Often Domain and Path are implicit but they do have defined values on the client.
Now to delete a cookie with identity (Domain-X, Path-X, Name-X) you must send a cookie with the same identity (Domain-X, Path-X, Name-X) and MaxAge=-1. But as explained above the cookie you receive doesn't contain Domain and Path.
There are two ways out:
You must know whether your cookies are domain or host cookies and which path they were set for and use that information to delete them. (I would recommend this.)
Delete all possible cookies. Upon a request to path /foo/bar/wuz the cookies from the client might stem from path /, /foo or /foo/bar (if I remember correctly; test and look it up in RFC 6265). So delete the cookie with name "Name-X" for all these paths. Do the same for the Domain attribute which unfortunately is more complicated. Delete the host cookie (Domain=="") and delete the domain cookies (Domain!=""). Make sure to get the right domain name (the effective TLD plus one).
As you see 2 is pretty complicated. But that is how cookies are designed: The server is expected to know what cookies the servers sets i.e. the server is expected to know the cookie identity (Domain,Path,Name) of all its cookies. The responsibility of the client is to send back the appropriate (Name,Value) pair for a certain request only. If the server wishes to delete a cookie it just sets MaxAge of that cookie to -1. Note that "that cookie" is something the server is expected to know and not infer from a client request.

Storing oAuth state token in Flask session

A couple of tutorials on oAuth use the Flask session to store state parameters and access tokens in the flask session. (Brendan McCollam's very useful presentation from Pycon is an example)
I understand that Flask stores the session in cookies on the client side and that they are fairly easy to expose (see Michael Grinberg's how-secure-is-the-flask-user-session). I tried this myself and was able to see the token the expiration, etc.
Is it correct to store the state and tokens in the flask session or they should be stored somewhere else?
Code example:
#app.route('/login', methods=['GET'])
def login():
provider = OAuth2Session(
client_id=CONFIG['client_id'],
scope=CONFIG['scope'],
redirect_uri=CONFIG['redirect_uri'])
url, state = provider.authorization_url(CONFIG['auth_url'])
session['oauth2_state'] = state
return redirect(url)
#app.route('/callback', methods=['GET'])
def callback():
provider = OAuth2Session(CONFIG['client_id'],
redirect_uri=CONFIG['redirect_uri'],
state=session['oauth2_state'])
token_response = provider.fetch_token(
token_url=CONFIG['token_url'],
client_secret=CONFIG['client_secret'],
authorization_response=request.url)
session['access_token'] = token_response['access_token']
session['access_token_expires'] = token_response['expires_at']
transfers = provider.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1')
return redirect(url_for('index'))
#app.route('/')
def index():
if 'access_token' not in session:
return redirect(url_for('login'))
transfers = requests.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1',
headers={'Authorization': 'Bearer ' + session['access_token']})
return render_template('index.html.jinja2',
transfers=transfers.json())
I think some tutorials over-simplify in order to show simpler code. A good rule of thumb is to use session cookies only for information that MUST be known by your application and your user's browser, and is not private. That normally translates into a Session ID and possibly other non sensitive information such as a language selection.
Applying that rule of thumb, I'd suggest the next to each of the tokens:
Authorization Token: this data is by definition known to both the user and the application, so it shouldn't be a security concern to expose it in the cookie. However, there really is no need to keep this token once you're given an access code, so I advice against keeping it locally or in your cookies.
Access Code: this data must be considered secret, and must only be known by your application and the provider. There is no reason to make it know to any other parties, including the user, therefore it should NOT be included in cookies. If you need to store it, keep it locally in your servers (perhaps in your database, referencing your users session ID).
CSRF State Token: this data is ideally included as a hidden form field and validated against a server side variable, so cookies seem like an unnecessary complication. But I wouldn't be concerned about this data being in a cookie, since it's part of the response anyways.
Keep in mind there are extensions such as flask-sessions, with which practically the same code uses server side variables instead of cookie variables.

Have Dynamical values for my parameters in the Request payload (POST x-www-form-urlencoded)

Is there a way (and does it make sense even) to have dynamical values for my request parameters (in my case POST application/x-www-form-urlencoded that has two parameters username and password) which can be altered based on some function or a returned value from the server from a previous request?
The motivation being that i have a register-new-user request which i run from time to time off apiary.io and unless i manually change the example value for the username i get a "use already exists" response instead of 201 i want (since this request was already run with the username in the example).
What i'd like to have instead is a value in the API documentation that will change on each execution of the API call (either using some random number there, or to be able to have it take a value returned from a previous request).
Is there anything you can suggest to solve my "user already exists" response for register-new-user API call?
Here is my current API documentation (the relevant part):
## Registration [/users.json]
The `/users.json` resource handles registration of new user
### Register a New Patient [POST]
Register a new patient action sends email and password and registers
the new user in case it doesn't already exist
+ Request (application/x-www-form-urlencoded)
+ Attributes (Test User)
+ Body
user[email]=username#example.com&user[password]=123456
+ Response 201 (application/json)
{
"id":500
}
# Data Structures
## Test User (object)
+ "user[email]" (string): "username#example.com" - user email
+ "user[password]" (string): "123456" - user password
Thanks in advance
You can partially simulate this in the Apiary mock server by passing a header in your call, for example:
Prefer: status=200
See https://help.apiary.io/tools/mock-server/#multiple-responses
In general the mock server is not yet flexible and programmable enough to fully do what you describe, for example conditionals, dynamic variables or random responses.
We are working on enhancing this. If you'd like you may comment here on your requirements:
https://github.com/apiaryio/api-blueprint/issues/58
Feel free to also ping us in Apiary (in-app chat) or on support#apiary.io.
Thanks

Resources