anydistro / bxt

Next generation repository maintenance tool (WIP)
GNU Affero General Public License v3.0
0 stars 4 forks source link

Implement token refreshment #54

Closed LordTermor closed 4 months ago

LordTermor commented 4 months ago

Implement token expiration and refreshment in some period of time.

fhdk commented 4 months ago

May I suggest using the Authorization header of type Bearer ?

May I suggest - instead of using a cookie - return json encoded data in the response body ideally with information about issued_at and expires_in or something similar?

As of now one send a urlencoded form with username and password - may I suggest that you add an argument to the form - indicating which type of authentication request - a grant_type with value password or refresh_token?

Example authorization request using password grant

grant_type:password
username:{{user}}
password:{{password}}

Example request using refresh_token grant

grant_type:refresh_token
refresh_token:{{refresh-token}}

Both requests returns a json body with a token and a matching refresh_token - both changes upon receiving a new authenticattion request

{
    "access_token": "EjVACgYpR4XDRePFa5Q6HgWH9FiL9ARy56h7n69lcgmgZ1YNiMEyWHdkgnsJmm8e4PKT5MKtkhPUMS2Nm_09-a_c9fhopRjNUB3_VEARi7J2eI5N6QEq_UMeHlYd7xzEW6Es65aOEK9cJ6cjnV1jAuT_ddtuuLF-S2IjIUwmwmhOMMUGrAk0NguIQ4dGrqJo0oQf2iAVRv1ghTxstTTlz1PeMJ76sD3SYsST6iOlDjnSqhcWRjOHlA-rpafw6Ua39pF7X4fl6rtMtmnM3DRJIF2Tkja1IkPcnTLHbyPqv-LvCimY5-KXJXUteIcCDovGTyKEl1Cm_sDlUcie5MYUCKf8uUJ2Mhp6DO_q7ifPMRjBWw3bkSU8zhyMpNfpuE5usmqbL4e3vAyDvE5Z1Hf_D0-3JDh5vQZKyp0PN_JGFUfjMGsLK1k-gcR9uGV16fpj7iPGuyosvXZdr2FsmkA5m9v6hmoECPE_-sQgS4XzovtSxQMMpU3laD7SFpR8W8r2",
    "token_type": "bearer",
    "expires_in": 604799,
    "refresh_token": "RuBSQlRoJpzKbfsObgKsJiBKJfXGfjJHiAGh+OAB49k=",
    ".issued": "Tue, 28 May 2024 13:14:26 GMT",
    ".expires": "Tue, 04 Jun 2024 13:14:26 GMT"
}

Sample request header

Authorization: bearer EjVACgYpR4XDRePFa5Q6HgWH9FiL9ARy56h7n69lcgmgZ1YNiMEyWHdkgnsJmm8e4PKT5MKtkhPUMS2Nm_09-a_c9fhopRjNUB3_VEARi7J2eI5N6QEq_UMeHlYd7xzEW6Es65aOEK9cJ6cjnV1jAuT_ddtuuLF-S2IjIUwmwmhOMMUGrAk0NguIQ4dGrqJo0oQf2iAVRv1ghTxstTTlz1PeMJ76sD3SYsST6iOlDjnSqhcWRjOHlA-rpafw6Ua39pF7X4fl6rtMtmnM3DRJIF2Tkja1IkPcnTLHbyPqv-LvCimY5-KXJXUteIcCDovGTyKEl1Cm_sDlUcie5MYUCKf8uUJ2Mhp6DO_q7ifPMRjBWw3bkSU8zhyMpNfpuE5usmqbL4e3vAyDvE5Z1Hf_D0-3JDh5vQZKyp0PN_JGFUfjMGsLK1k-gcR9uGV16fpj7iPGuyosvXZdr2FsmkA5m9v6hmoECPE_-sQgS4XzovtSxQMMpU3laD7SFpR8W8r2

Sample request for new authentication using a valid refresh_token

grant_type:refresh_token
refresh_token:RuBSQlRoJpzKbfsObgKsJiBKJfXGfjJHiAGh+OAB49k=
LordTermor commented 4 months ago

While overall my idea with refresh token is roughly the same, I don't think it's a good idea to

using the Authorization header of type Bearer

As in web you'd need to store token in the local storage which is more vulnerable to cross-site scripting attacks.

fhdk commented 4 months ago

I understand the concerns - when it comes to web clients - there is no easy way around it - except to limit the validity of a token.

However - when it comes to API - and the command line interface - the storage of token in some local form is crucial to the experience. You can set the session as you see fit for the web interface - I will never say anything - but the CLI needs a little more flexibility.

The idea of using a token is to avoid storing the username password locally on the system - only using the tokens - which then become independent of the username - as the token is the credential used.

Using a refresh token will require storing the refresh token - and as a browser only has one place - local storage - which makes it possible to use the refresh token at any given point in time to fetch a new access token - then I don't understand why you would need the refresh token at all.

I mean - if the access token is not valid - prompt for password - when a refresh token is present - you never prompt for password but uses the refresh token - which makes - for the point of cross site scripting - the use of refresh token less secure than it is to prompt the user for the password.

A properly CORS setup will minimize that specific attackvector considerably.

That is why the concept of token/refresh token exist even big companies like Microsoft uses it in Production with their Azure cloud access - provide a couple of secrets - you get a token which is valid for 60m.

The company I work for uses this with the scope of their applications. Due to the limited validity - there is no real threat with the mentioned attack vector - suffice the validity period is short.

I use a similiar technique - correct credentials give you a ticket which is valid for one week and refresh ticket valid for 4 weeks. When the ticket expires the refresh ticket provides a new ticket - the refresh ticket used is expired - this runs on a loop.

fhdk commented 4 months ago

If you try to test the API in postman - you will see that you cannot set a cookie token.

You can set it using header or in query - you can even select an Authentication header with a JWT token - but not in a session cookie.

What you do is understandable when you look at your own web client - but other interaction usually requires the Authentication header.

LordTermor commented 4 months ago

but the CLI needs a little more flexibility.

What specific flexibility does it need? Can't it store cookies on disk?

a browser only has one place - local storage

You can store a refresh token in cookies as well.

you will see that you cannot set a cookie token

I know that you can't, it's their purpose. You can however get cookies by performing auth and store them on disk to load on demand.

fhdk commented 4 months ago

What specific flexibility does it need?

The flexibility is such bad word - I shouldn't have chosen it - what I meant is - follow standards for providing the authentication - in the authentication header

Authentication : bearer token-bla-bla

Can't it store cookies on disk?

Of course - and it does

What I mean - when it comes to providing the token to the API why are you not using the standard?

The Authentication is for that very purpose - to provide the credentials to the endpoint.

I made the same mistake once - thinking the approach as the web application - and that is fine for a dynamic website generating pages based on the credentials of the logged in user.

But when you supply an API the usage changes too.

It would be fine to provide the token as part of the request

https://...........?token=bla&branch=....
LordTermor commented 4 months ago

What I mean - when it comes to providing the token to the API why are you not using the standard?

Literally every resource I read says that you should store JWTs as a HttpOnly cookie to limit the attack possibilities.

EDIT: I can provide an additional way to authorize using Bearer token for CLI if you really think you need it.

fhdk commented 4 months ago

Literally every resource I read says that you should store JWTs as a HttpOnly cookie to limit the attack possibilities.

And that is correct for a browser client.

The CLI is not a browser - and it need another way to deliver the token - usually the Authentication header - which is also the reply I get when I fetch a resource without token

{'message': 'Authentification header is not found', 'status': 'error'}

While at the Authentication vs. Authorization and http response code.

The difference between 401 and 403 is

If I my token is valid for resource X but I request resource Y the correct response is 403 - thus indicating you may need another set of credentials.

But if my token is expired the correct response is 401

Do you see the difference?

LordTermor commented 4 months ago

I use 403 for insufficient permissions starting with https://github.com/anydistro/bxt/pull/51

fhdk commented 4 months ago

Another reason for using the Authentication header is that a cookie is usually set by the server when interacting with browser.

The standard for machine to machine access - usually using scripts or command line utilities - requires the machine to be able to provide the credentials with the request and to read directly from the response.

Thus the requirement to be able to read the token values from the response body - specifying Accept header to application/json - the client indicating - I need the result in the response body as json - thank you.

LordTermor commented 4 months ago

indicating which type of authentication request - a grant_type with value password or refresh_token?

It's a part of OAuth 2.0 standard which I don't use now as we don't have any external auth server. It's for sure a room for extension if we want to integrate something like single sign-on but for now I'll implement fairly basic access-refresh tokens model.

fhdk commented 4 months ago

I will work with what you decide. I use OAuth standard - using an internal implementation, within the API - selfhosted one could say.

It was merely a suggestion on how to differentiate the token request methods but you could do that with endpoint.