Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom data to be sent inside access tokens #1602

Merged
merged 2 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/doorkeeper/authorizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def pre_auth_params
end

def pre_auth_param_fields
%i[
Doorkeeper.configuration.custom_access_token_attributes + %i[
client_id
code_challenge
code_challenge_method
Expand Down
9 changes: 9 additions & 0 deletions lib/doorkeeper/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,15 @@ def configure_secrets_for(type, using:, fallback:)
option :access_token_generator,
default: "Doorkeeper::OAuth::Helpers::UniqueToken"

# Allows additional data to be received when granting access to an Application, and for this
# additional data to be sent with subsequently generated access tokens. The access grant and
# access token models will both need to respond to the specified attribute names.
#
# @param attributes [Array] The array of custom attribute names to be saved
#
option :custom_access_token_attributes,
default: []

# Use a custom class for generating the application secret.
# https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-application-secret-generator
#
Expand Down
12 changes: 12 additions & 0 deletions lib/doorkeeper/config/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ def validate_token_reuse_limit
)
@token_reuse_limit = 100
end

def validate_custom_access_token_attributes
# Validate that the access_token and access_grant models
# both respond to all of the custom attributes
Doorkeeper.config.custom_access_token_attributes.each do |attribute_name|
[Doorkeeper.config.access_token_model, Doorkeeper.config.access_grant_model].each do |model|
unless model.has_attribute?(attribute_name)
raise NotImplementedError, "#{model} does not recognize custom attribute: #{attribute_name}."
end
end
end
end
end
end
end
6 changes: 6 additions & 0 deletions lib/doorkeeper/oauth/authorization/code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ def access_grant_attributes
attributes[:resource_owner_id] = resource_owner.id
end

# Custom access token attributes are saved into the access grant,
# and then included in subsequently generated access tokens.
Doorkeeper.config.custom_access_token_attributes.each do |attribute_name|
attributes[attribute_name] = @pre_auth.custom_access_token_attributes[attribute_name]
end

pkce_attributes.merge(attributes)
end

Expand Down
5 changes: 5 additions & 0 deletions lib/doorkeeper/oauth/authorization_code_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def before_successful_response
grant.application,
resource_owner,
grant.scopes,
custom_token_attributes_with_data,
server,
)
end
Expand Down Expand Up @@ -99,6 +100,10 @@ def validate_code_verifier
def generate_code_challenge(code_verifier)
server_config.access_grant_model.generate_code_challenge(code_verifier)
end

private def custom_token_attributes_with_data
grant.attributes.with_indifferent_access.slice(*Doorkeeper.config.custom_access_token_attributes).symbolize_keys
end
end
end
end
13 changes: 9 additions & 4 deletions lib/doorkeeper/oauth/base_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ def scopes
@scopes ||= build_scopes
end

def find_or_create_access_token(client, resource_owner, scopes, server)
def find_or_create_access_token(client, resource_owner, scopes, custom_attributes, server)
context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
@access_token = server_config.access_token_model.find_or_create_for(
application: client.is_a?(server_config.application_model) ? client : client&.application,
token_model = server_config.access_token_model
application = client.is_a?(server_config.application_model) ? client : client&.application

token_attributes = {
application: application,
resource_owner: resource_owner,
scopes: scopes,
expires_in: Authorization::Token.access_token_expires_in(server, context),
use_refresh_token: Authorization::Token.refresh_token_enabled?(server, context),
)
}

@access_token = token_model.find_or_create_for(**token_attributes.merge(custom_attributes))
end

def before_successful_response
Expand Down
2 changes: 1 addition & 1 deletion lib/doorkeeper/oauth/password_access_token_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def initialize(server, client, credentials, resource_owner, parameters = {})
private

def before_successful_response
find_or_create_access_token(client, resource_owner, scopes, server)
find_or_create_access_token(client, resource_owner, scopes, {}, server)
super
end

Expand Down
7 changes: 6 additions & 1 deletion lib/doorkeeper/oauth/pre_authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PreAuthorization

attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
:redirect_uri, :resource_owner, :response_type, :state,
:authorization_response_flow, :response_mode
:authorization_response_flow, :response_mode, :custom_access_token_attributes

def initialize(server, parameters = {}, resource_owner = nil)
@server = server
Expand All @@ -31,6 +31,11 @@ def initialize(server, parameters = {}, resource_owner = nil)
@code_challenge = parameters[:code_challenge]
@code_challenge_method = parameters[:code_challenge_method]
@resource_owner = resource_owner

@custom_access_token_attributes = {}
Doorkeeper.config.custom_access_token_attributes.each do |field|
@custom_access_token_attributes[field] = parameters[field]
end
end

def authorizable?
Expand Down
17 changes: 17 additions & 0 deletions lib/generators/doorkeeper/templates/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,23 @@
# resource_owner.admin? || client.owners_allowlist.include?(resource_owner)
# end

# Allows additional data fields to be sent while granting access to an application,
# and for this additional data to be included in subsequently generated access tokens.
# The 'authorizations/new' page will need to be overridden to include this additional data
# in the request params when granting access. The access grant and access token models
# will both need to respond to these additional data fields, and have a database column
# to store them in.
#
# Example:
# You have a multi-tenanted platform and want to be able to grant access to a specific
# tenant, rather than all the tenants a user has access to. You can use this config
# option to specify that a ':tenant_id' will be passed when authorizing. This tenant_id
# will be included in the access tokens. When a request is made with one of these access
# tokens, you can check that the requested data belongs to the specified tenant.
#
# Default value is an empty Array: []
# custom_access_token_attributes [:tenant_id]

# Hook into the strategies' request & response life-cycle in case your
# application needs advanced customization or logging:
#
Expand Down
14 changes: 14 additions & 0 deletions spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,20 @@ class ApplicationWithOwner < ActiveRecord::Base
end
end

describe "custom_access_token_attributes" do
it "is '[]' by default" do
expect(Doorkeeper.configuration.custom_access_token_attributes).to(eq([]))
end

it "can change the value" do
Doorkeeper.configure do
orm DOORKEEPER_ORM
custom_access_token_attributes [:added_field_1, :added_field_2]
end
expect(config.custom_access_token_attributes).to eq([:added_field_1, :added_field_2])
end
end

describe "application_secret_generator" do
it "is 'Doorkeeper::OAuth::Helpers::UniqueToken' by default" do
expect(Doorkeeper.configuration.application_secret_generator).to(
Expand Down
4 changes: 4 additions & 0 deletions spec/lib/oauth/base_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
client,
resource_owner,
"public",
{},
server,
)

Expand All @@ -144,6 +145,7 @@
client,
resource_owner,
"public",
{},
server,
)
expect(result.expires_in).to be(500)
Expand All @@ -165,6 +167,7 @@
client,
resource_owner,
"public",
{},
server,
)
expect(result.refresh_token).not_to be_nil
Expand All @@ -173,6 +176,7 @@
client,
resource_owner,
"private",
{},
server,
)
expect(result.refresh_token).to be_nil
Expand Down