README.md in rodauth-oauth-0.0.1 vs README.md in rodauth-oauth-0.0.2

- old
+ new

@@ -1,11 +1,11 @@ # Rodauth::Oauth [![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/commits/master) [![coverage report](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/coverage.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/commits/master) -This is an extension to the `rodauth` gem which adds support for the [OAuth 2.0 protocol](https://tools.ietf.org/html/rfc6749). +This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server. ## Features This gem implements: @@ -13,10 +13,11 @@ * [Authorization grant flow](https://tools.ietf.org/html/rfc6749#section-1.3); * [Access Token generation](https://tools.ietf.org/html/rfc6749#section-1.4); * [Access Token refresh](https://tools.ietf.org/html/rfc6749#section-1.5); * [Token revocation](https://tools.ietf.org/html/rfc7009); * [Implicit grant (off by default)[https://tools.ietf.org/html/rfc6749#section-4.2]; + * [PKCE](https://tools.ietf.org/html/rfc7636); * Access Type (Token refresh online and offline); * OAuth application and token management dashboards; This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))). @@ -94,14 +95,13 @@ ##### HTTPX ```ruby require "httpx" -httpx = HTTPX.plugin(:authorization) -response = httpx.with(headers: { "X-your-auth-scheme" => ENV["SERVER_KEY"] }) - .post("https://auth_server/oauth-token",json: { +response = HTTPX.post("https://auth_server/oauth-token",json: { client_id: ENV["OAUTH_CLIENT_ID"], + client_secret: ENV["OAUTH_CLIENT_SECRET"], grant_type: "authorization_code", code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as" }) response.raise_for_status payload = JSON.parse(response.to_s) @@ -109,25 +109,24 @@ ``` ##### cURL ``` -> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","grant_type":"authorization_code","code":"oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"}' https://auth_server/oauth-token +> curl --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"authorization_code","code":"oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as"}' https://auth_server/oauth-token ``` #### Refresh Token Refreshing expired tokens also happens mostly server-to-server, here's an example: ##### HTTPX ```ruby require "httpx" -httpx = HTTPX.plugin(:authorization) -response = httpx.with(headers: { "X-your-auth-scheme" => ENV["SERVER_KEY"] }) - .post("https://auth_server/oauth-token",json: { +response = HTTPX.post("https://auth_server/oauth-token",json: { client_id: ENV["OAUTH_CLIENT_ID"], + client_secret: ENV["OAUTH_CLIENT_SECRET"], grant_type: "refresh_token", token: "2r89hfef4j9f90d2j2390jf390g" }) response.raise_for_status payload = JSON.parse(response.to_s) @@ -135,11 +134,11 @@ ``` ##### cURL ``` -> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","grant_type":"token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/oauth-token +> curl -H "X-your-auth-scheme: $SERVER_KEY" --data '{"client_id":"$OAUTH_CLIENT_ID","client_secret":"$OAUTH_CLIENT_SECRET","grant_type":"token","token":"2r89hfef4j9f90d2j2390jf390g"}' https://auth_server/oauth-token ``` #### Revoking tokens Token revocation can be done both by the idenntity owner or the application owner, and can therefore be done either online (browser-based form) or server-to-server. Here's an example using server-to-server: @@ -289,19 +288,64 @@ ## Features In this section, the non-standard features are going to be described in more detail. +### Token / Secrets Hashing + +Although not human-friendly as passwords, for security reasons, you might not want to store access (and refresh) tokens in the database. If that is the case, You'll have to add the respective hash columns in the table: + +```ruby +# in migration +String :token_hash, null: false, token: true +String :refresh_token_hash, token, true +# and you DO NOT NEED the token and refresh_token columns anymore! +``` + +And declare them in the plugin: + +```ruby +plugin :rodauth do + enable :oauth + oauth_tokens_token_hash_column :token_hash + oauth_tokens_token_hash_column :refresh_token_hash +``` + +#### Client Secret + +By default, it's expected that the "client secret" property from an OAuth application is only known by the owner, and only the hash is stored in the database; this way, the authorization server doesn't know what the client secret is, only the application owner. The provided [OAuth Applications Extensions](#oauth-applications) application form contains a "Client Secret" input field for this reason. + +However, this extension is optional, and you might want to generate the secrets and store them as is. In that case, you'll have to re-define some options: + +```ruby +plugin :rodauth do + enable :oauth + secret_matches? ->(application, secret){ application[:client_secret] == secret } +end +``` + ### Access Type (default: "offline") The "access_type" feature allows the authorization server to emit access tokens with no associated refresh token. This means that users with expired access tokens will have to go through the OAuth flow everytime they need a new one. In order to enable this option, add "access_type=online" to the query params section of the authorization url. -**Note**: this feature does not yet support the "approval_prompt" feature. +#### Approval Prompt +When using "online grants", one can use an extra query param in the URL, "approval_prompt", which when set to "auto", will skip the authorization form (on the other hand, if one wants to force the authorization form for all grants, then you can set it to "force", or don't set it at all, as it's the default). +This will only work **if there was a previous successful online grant** for the same application, scopes and redirect URI. + +#### DB schema + +the "oauth_grants" table will have to include the "access_type row": + +```ruby +# in migration +String :access_type, null: false, default: "offline" +``` + ### Implicit Grant (default: disabled) The implicit grant flow is part of the original OAuth 2.0 RFC, however, if you care about security, you are **strongly recommended** not to enable it. However, if you really need it, just pass the option when enabling the `rodauth` plugin: @@ -312,9 +356,47 @@ use_oauth_implicit_grant_type true end ``` And add "response_type=token" to the query params section of the authorization url. + +### PKCE + +The "Proof Key for Code Exchange by OAuth Public Clients" (aka PKCE) flow, which is **particularly recommended for OAuth integration in mobile apps**, is transparently supported by `rodauth-oauth`, by adding the `code_challenge_method=S256&code_challenge=$YOUR_CODE_CHALLENGE` query params to the authorization url. Once you do that, you'll have to pass the `code_verifier` when generating a token: + +```ruby +# with httpx +require "httpx" +httpx = HTTPX.plugin(:authorization) +response = httpx.with(headers: { "X-your-auth-scheme" => ENV["SERVER_KEY"] }) + .post("https://auth_server/oauth-token",json: { + client_id: ENV["OAUTH_CLIENT_ID"], + grant_type: "authorization_code", + code: "oiweicnewdh32fhoi3hf3ihfo2ih3f2o3as", + code_verifier: your_code_verifier_here + }) +response.raise_for_status +payload = JSON.parse(response.to_s) +puts payload #=> {"token" = +``` + +By default, the pkce integration sets "S256" as the default challenge method. If you value security, you **should not use plain**. However, if you really need to, you can set it in the `rodauth` plugin: + +```ruby +plugin :rodauth do + enable :oauth + oauth_pkce_challenge_method "plain" +end +``` + +Although PKCE flow is supported out-of-the-box, it's not enforced by default. If you want to, you can force it, thereby forcing clients to generate a challenge: + +```ruby +plugin :rodauth do + enable :oauth + oauth_require_pkce true +end +``` ## Ruby support policy The minimum Ruby version required to run `rodauth-oauth` is 2.3 . Besides that, it should support all rubies that rodauth and roda support.