Atlassian JWT Authentication provides support for handling JWT authentication as required by Atlassian when building add-ons: https://developer.atlassian.com/static/connect/docs/latest/concepts/authentication.html
You can check out the latest source from git:
git clone https://github.com/MeisterLabs/atlassian-jwt-authentication.git
Or, if you're using Bundler, just add the following to your Gemfile:
gem 'atlassian-jwt-authentication',
git: 'https://github.com/MeisterLabs/atlassian-jwt-authentication.git'
This gem relies on the jwt_tokens
table being present in your database and
the associated JwtToken model.
To create those simply use the provided generators:
bundle exec rails g atlassian_jwt_authentication:setup
If you are using another database for the JWT data storage than the default one, pass the name of the DB config to the generator:
bundle exec rails g atlassian_jwt_authentication:setup shared
Don't forget to run your migrations now!
The gem provides 2 endpoints for an Atlassian add-on lifecycle, installed and uninstalled. For more information on the available Atlassian lifecycle callbacks visit https://developer.atlassian.com/static/connect/docs/latest/modules/lifecycle.html.
If your add-on baseUrl is not your application root URL then include the following configuration for the context path. This is needed in the query hash string validation step of verifying the JWT:
# In the add-on descriptor:
# "baseUrl": "https://www.example.com/atlassian/confluence",
AtlassianJwtAuthentication.context_path = '/atlassian/confluence'
The gem will take care of setting up the necessary JWT tokens upon add-on installation and to delete the appropriate tokens upon un-installation. To use this functionality, simply call
include AtlassianJwtAuthentication
before_action :on_add_on_installed, only: [:installed]
before_action :on_add_on_uninstalled, only: [:uninstalled]
Furthermore, protect the methods that will be JWT aware by using the gem's JWT token verification filter. You need to pass your add-on descriptor so that the appropriate JWT shared secret can be identified:
include AtlassianJwtAuthentication
# will respond with head(:unauthorized) if verification fails
before_filter only: [:display, :editor] do |controller|
controller.send(:verify_jwt, 'your-add-on-key')
end
Methods that are protected by the verify_jwt
filter also give access to information
about the current JWT token instance and logged in account (when available):
current_jwt_token
returnsJwtToken
current_account_id
returnsString
Furthermore, this information is stored in the session so you will have access to these 2 instances also on subsequent requests even if they are not JWT signed.
# current_jwt_token returns an instance of JwtToken, so you have access to the fields described above
pp current_jwt_token.addon_key
pp current_jwt_token.base_url
If you need detailed user information you need to obtain it from the instance and process it respecting GDPR.
If your add-on has a licensing model you can use the ensure_license
filter to check for a valid license.
As with the verify_jwt
filter, this simply responds with an unauthorized header if there is no valid license
for the installation.
before_filter :ensure_license
If your add-on was for free and you're just adding licensing now, you can specify the version at which you started charging, ie. the minimum version of the add-on for which you require a valid license. Simply include the code below with your version string in the controller that includes the other add-on code.
def min_licensing_version
Gem::Version.new('1.0.0')
end
You can use a middleware to verify JWT tokens (for example in Rails application.rb
):
config.middleware.insert_after ActionDispatch::Session::CookieStore, AtlassianJwtAuthentication::Middleware::VerifyJwtToken, 'your_addon_key'
Token will be taken from params or Authorization
header, if it's verified successfully request will have following headers set:
- atlassian_jwt_authorization.jwt_token
JwtToken
instance - atlassian_jwt_authorization.account_id
String
instance - atlassian_jwt_authorization.context
Hash
instance
Middleware will not block requests with invalid or missing JWT tokens, you need to use another layer for that.
Build the URL required to make a service call with the rest_api_url
helper or
make a service call with the rest_api_call
helper that will handle the request for you.
Both require the method and the endpoint that you need to access:
# Get available project types
url = rest_api_url(:get, '/rest/api/2/project/type')
response = Faraday.get(url)
# Create an issue
data = {
fields: {
project: {
'id': 10100
},
summary: 'This is an issue summary',
issuetype: {
id: 10200
}
}
}
response = rest_api_call(:post, '/rest/api/2/issue', data)
pp response.success?
To make requests on user's behalf add act_as_user
in scopes required by your app.
Later you can obtain OAuth bearer token from Atlassian.
Do that using AtlassianJwtAuthentication::UserBearerToken.user_bearer_token(account_id, scopes)
If you want to debug the JWT verification define a logger in the controller where you're including AtlassianJwtAuthentication
:
def logger
Logger.new("#{Rails.root}/log/atlassian_jwt.log")
end
If you want to render your own pages when the add-on throws one of the following errors:
- forbidden
- unauthorized
- payment_required
overwrite the following methods in your controller:
def render_forbidden
# do your own handling there
# render your own template
render(template: '...', layout: '...')
end
# the same for render_payment_required and render_unauthorized
You can use rake tasks to simplify plugin installation:
bin/rails atlassian:install[prefix,email,api_token,https://external.address.to/descriptor]
Where prefix
is your instance name before .atlassian.net
. You an get an API token from Manage your account page.
Config | Environment variable | Description | Default |
---|---|---|---|
AtlassianJwtAuthentication.context_path |
none | server path your app is running at | '' |
AtlassianJwtAuthentication.verify_jwt_expiration |
JWT_VERIFY_EXPIRATION |
when false allow expired tokens, speeds up development, especially combined with webpack hot module reloading |
true |
AtlassianJwtAuthentication.log_requests |
AJA_LOG_REQUESTS |
when true outgoing HTTP requests will be logged |
false |
AtlassianJwtAuthentication.debug_requests |
AJA_DEBUG_REQUESTS |
when true HTTP requests will include body content, implicitly turns on log_requests |
false |
Ruby 2.0+, ActiveRecord 4.1+
With middleware enabled you can use following configuration to limit access to message bus per user / instance:
MessageBus.user_id_lookup do |env|
env.try(:[], 'atlassian_jwt_authentication.account_id')
end
MessageBus.site_id_lookup do |env|
env.try(:[], 'atlassian_jwt_authentication.jwt_token').try(:id)
end
Then use MessageBus.publish('/test', 'message', site_id: X, user_ids: [Y])
to publish message only for a user.
Requires message_bus patch available at https://github.com/HeroCoders/message_bus/commit/cd7c752fe85a17f7e54aa950a94d7c6378a55ed1
Removed current_jwt_user
, JwtUser
, update your code to use current_account_id
current_jwt_auth
has been renamed to current_jwt_token
to match model name. Either mass rename or add alias
in your controller:
alias_method :current_jwt_auth, :current_jwt_token
helper_method :current_jwt_auth