Skip to content

Conversation

@christianarty
Copy link

@christianarty christianarty commented Jul 17, 2025

Summary

fixes #863

This PR allows for users to have another option (oauth) when generating their JIRA config.yml for the Cloud installation.

Details

This PR implements JIRA's 3LO OAuth solution for users to obtain a JIRA access token.
Each consumer of jira-cli will need to create a JIRA App with the specific scopes in order to connect it properly with their JIRA cloud instance.

The oauth secret will be stored in the .config/.jira directory, where the tokens will be automatically regenerated when it expires and the newly generated tokens will be cached to the oauth secret file.

How to create a JIRA App properly:

See this discussion post here: #879 (comment)

Known Limitations/Issues

Note

This limitation has also been noted in the README under the Known Issues section.

Ideally, for OAuth, we would have one single distributed app that can be installed in multiple different JIRA cloud instances. However, The 3LO doesn't support Proof Key for Code Exchange (PKCE). Without this support, we would have to share the single distrubuted app's client secret with all the consumers. See these links for more info:

As noted in the forum above, a workaround would be that each consumer has to create their own JIRA app and use that app's client ID and secret in the jira-cli client app.

  • This basically acts like a proxy to funnel requests into your JIRA cloud instance

Testing Done

  • make deps install => WORKS
  • ~/go/bin/jira issue create -tTask -s"TEST TICKET" -l"testing" --template ~/jira/task.tmpl -a$(~/go/bin/jira me) => WORKS (created a ticket, and proper link)
  • make test => WORKS
  • make lint => WORKS
  • make ci => WORKS

@christianarty christianarty marked this pull request as ready for review July 17, 2025 12:45
@shadyabhi
Copy link

cc: @ankitpokhrel for visibility. It'll be great if this can be reviewed/merged soon, so folks that can only authenticate through OAuth 3LO can start using this tool again.

@christianarty
Copy link
Author

@ankitpokhrel Could you take a look at this PR when you get a chance? I think this is a nice feature add (and personally been using it for months now without hiccup)

@ankitpokhrel
Copy link
Owner

@christianarty Thanks for the PR! I'm currently away, I'll look into this in a few weeks.

@christianarty
Copy link
Author

@ankitpokhrel Hey! Just wanted to see if you had a moment to take a peek at this PR! Would be appreciated thanks!

@christianarty
Copy link
Author

christianarty commented Oct 8, 2025

Also added a new commit that expanded the default scopes since recently realized, with the oauth, for jira sprint list to work properly needed extra scopes: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-sprintid-issue-get
https://developer.atlassian.com/cloud/jira/software/rest/api-group-sprint/#api-rest-agile-1-0-sprint-sprintid-issue-post

@ankitpokhrel ankitpokhrel requested a review from Copilot October 11, 2025 09:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds OAuth 3LO (3-legged OAuth) authentication support to the JIRA CLI, allowing users to authenticate with Atlassian Jira Cloud using OAuth instead of API tokens. The implementation includes a complete OAuth flow with automatic token refresh, secure credential storage, and cloud ID retrieval for proper API access.

  • Implements OAuth 3LO authentication flow with automatic browser-based authorization
  • Adds secure file-based storage for OAuth tokens and client credentials
  • Integrates OAuth authentication into existing JIRA client with automatic token refresh

Reviewed Changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/oauth/oauth.go Core OAuth implementation with flow management and cloud ID retrieval
pkg/oauth/tokens.go Token management with automatic refresh and persistent storage
pkg/oauth/oauth_test.go Comprehensive test suite for OAuth functionality
pkg/utils/storage.go Generic file storage interface with JSON serialization helpers
pkg/utils/storage_test.go Test coverage for storage functionality
pkg/jira/client.go Integration of OAuth transport into JIRA client
internal/config/generator.go Configuration generator updates for OAuth setup
api/client.go Client factory updates to support OAuth token sources
pkg/jira/types.go New authentication type constants
pkg/jira/cloud_id.go Cloud ID retrieval functionality
README.md Documentation updates for OAuth authentication

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +283 to +355
ctx, cancel := context.WithTimeout(context.Background(), serverShutdownTimeout)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Printf("Warning: failed to shutdown server: %v\n", err)
}
Copy link

Copilot AI Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server shutdown logic is duplicated in three places (lines 283-287, 303-307, 312-316). Consider extracting this into a helper function to reduce code duplication.

Copilot uses AI. Check for mistakes.
transport.TLSClientConfig.RootCAs = caCertPool
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
transport.TLSClientConfig.Renegotiation = tls.RenegotiateFreelyAsClient
tlsConfig := transport.(*http.Transport).TLSClientConfig
Copy link

Copilot AI Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type assertion will panic if the transport is not an *http.Transport. After adding OAuth support, the transport could be an *oauth2.Transport, making this assertion unsafe. Add a type check before the assertion.

Suggested change
tlsConfig := transport.(*http.Transport).TLSClientConfig
httpTransport, ok := transport.(*http.Transport)
if !ok {
log.Fatal("transport is not an *http.Transport; cannot configure mTLS")
}
tlsConfig := httpTransport.TLSClientConfig

Copilot uses AI. Check for mistakes.
Copy link
Owner

@ankitpokhrel ankitpokhrel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @christianarty, first of all, amazing work and sorry for jumping in a bit late!

I ran some tests, and most things are working great. I noticed a few things tho:

  1. Listing epic items returns 401. Perhaps we are missing some permissions?
➔ jira epic list ABC-3

jira: Received unexpected response '401 Unauthorized'.
Please check the parameters you supplied and try again.
  1. The warning is shown every time when oAuth is not used. I think we only want to show warning if the type is oAuth and api_server is not set.
Image
  1. Using jira init with Authentication type: cloud is failing with authentication err.
Image

Could you please help check?

(Feel free to ignore any copilot comment if that doesn't make sense)

Thank you again for great work! 🎉

Copy link
Author

@christianarty christianarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @christianarty, first of all, amazing work and sorry for jumping in a bit late!

I ran some tests, and most things are working great. I noticed a few things tho:

  1. Listing epic items returns 401. Perhaps we are missing some permissions?
➔ jira epic list ABC-3

jira: Received unexpected response '401 Unauthorized'.
Please check the parameters you supplied and try again.
  1. The warning is shown every time when oAuth is not used. I think we only want to show warning if the type is oAuth and api_server is not set.
Image
  1. Using jira init with Authentication type: cloud is failing with authentication err.
Image

Could you please help check?

(Feel free to ignore any copilot comment if that doesn't make sense)

Thank you again for great work! 🎉

Thanks for the information! Will address them.

Listing epic items returns 401. Perhaps we are missing some permissions?
After checking this url here for the endpoint we are hitting: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get

from my own mental model, the Classic scope should be sufficient, but then again the JIRA API is okay at best, so i added in all the other granular scopes that is stated there in the defaultScopes field, and will update the discussion post regarding it. With adding in all those scopes, it works as intended.

The warning is shown every time when oAuth is not used. I think we only want to show warning if the type is oAuth and api_server is not set.

I'll take a look at this in a bit to see what small thing is off

Using jira init with Authentication type: cloud is failing with authentication err.

I just fixed the logic error for when we checkJiraAPIToken basically if you already did oauth, it thinks it should use the oauth token. However i think the JIRA api has changed and tbh, i think we should migrate to use the v3 of the myself API: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-myself/#api-rest-api-3-myself-get

I am going to address these comments over the next couple days and when its ready to re-review i'll re-request a review.

@ccoVeille
Copy link

Also, I'm unsure if you can report the scope the app was created with without being authenticated, but it would be great to double check what us expected and what is currently on the created oauth app

Great suggestion! I am not sure about the scope when unauthorized and I think that should be a separate PR (this PR is already big enough tbh)

Yes, it does. And as we both said, we are not sure it can be done.

@ankitpokhrel ankitpokhrel requested a review from Copilot November 17, 2025 08:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@christianarty
Copy link
Author

Thanks @ccoVeille for the comments! Appreciate the extra look! I've addressed them all and tested it with the new revisions and things work as intended. @ankitpokhrel it now defaults to the keyring and fallsback to the filesystem storage. Please let me know if there is anything else we need to do in this PR otherwise i think its ready to 🚢

@christianarty
Copy link
Author

christianarty commented Nov 19, 2025

Screenshot 2025-11-18 at 7 49 49 PM

Also, @ccoVeille these are what the new order of expected scopes, alphabetically sorted and grouped by scope type.

@vibbix
Copy link

vibbix commented Nov 21, 2025

Hoping to see this merged soon, looks great!

@christianarty
Copy link
Author

@ankitpokhrel, whenever you get a chance could you drop a 🚢 . Would be greatly appreciated thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add new JIRA_AUTH_TYPE OAuth 2.0 3LO

5 participants