Skip to content

Support GitHub Apps for Authentication in Crowdin GitHub Action #270

Closed

Description

@Paul-Weaver

Feature Request: Support GitHub Apps for Authentication in Crowdin GitHub Action

Is your feature request related to a problem? Please describe.

Currently, the Crowdin GitHub Action requires a Personal Access Token (PAT) for authentication, which introduces security risks and compliance challenges. Many organizations, including those using GitHub Enterprise Managed Users (EMU), are transitioning away from PATs in favor of GitHub Apps, which provide:

  • Scoped permissions to minimize security risks.
  • Automatic token rotation to reduce manual management.
  • Better compliance with enterprise security policies.

Without GitHub Apps support, teams are forced to manage long-lived PATs, increasing the risk of token leaks, adding operational overhead, and potentially preventing adoption in environments with strict security policies.

This is critical for us due to security concerns, and we may be unable to continue using the Crowdin GitHub Action if GitHub Apps authentication is not supported.

Describe the solution you’d like

We request official support for GitHub Apps authentication in Crowdin GitHub Action, allowing:

  • Authentication via GitHub App tokens instead of manually managed PATs.
  • Support for GITHUB_TOKEN in workflows, leveraging GitHub’s built-in authentication with appropriate permissions.
  • A migration path from PAT-based authentication to GitHub Apps for existing users.

Describe alternatives you’ve considered

  • Continuing with PATs: Not ideal due to security risks, manual token rotation, and GitHub's push towards deprecating PAT usage in automation.

Additional context

Crowdin’s official GitHub Action (crowdin/github-action) currently requires PATs for pushing translations and creating pull requests.

  • GitHub officially recommends GitHub Apps for automation and has enabled GITHUB_TOKEN authentication for repository access.
  • Many organizations, including ours, will be unable to use Crowdin GitHub Action if GitHub Apps authentication is not supported.

This feature is critical for us, and we’d love to hear if it's on the roadmap. We're happy to assist with testing to ensure a smooth transition.

Looking forward to your response. Thanks!

Activity

  1. andrii-bodnar commented on Mar 5, 2025

    @andrii-bodnar
    Member

    Hi @Paul-Weaver, thanks for the request!

    There was a similar request in issue #264. Could you please check if the solution suggested in that issue works for you?

  2. Paul-Weaver commented on Mar 5, 2025

    @Paul-Weaver
    Author

    Hi @Paul-Weaver, thanks for the request!

    There was a similar request in issue #264. Could you please check if the solution suggested in that issue works for you?

    Thanks for the swift response, I will create GitHub App then test it

  3. Paul-Weaver commented on Mar 6, 2025

    @Paul-Weaver
    Author

    @andrii-bodnar it actually works on latest crowdin using GITHUB_TOKEN and having the app token there. Or am I misunderstanding something?

    name: crowdin-test-run
    
    on:
      workflow_dispatch:
    
    jobs:
      crowdin-test:
        runs-on: ubuntu-latest
        permissions:
          contents: write
          pull-requests: write
    
        outputs:
          pull_request_number: ${{ steps.download_translations.outputs.pull_request_number }}
    
        steps:
          - name: Checkout Repository
            uses: actions/checkout@v4
    
          - name: Generate GitHub App Token (Crowdin Test)
            id: generate-token
            uses: actions/create-github-app-token@v1
            with:
              app-id: ${{ vars.CROWDIN_APP_ID }}
              private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
    
          - id: download_translations
            name: Download Translations & Create PR (Crowdin Test)
            uses: crowdin/github-action@v2
            with:
              upload_sources: true
              download_translations: true
              upload_translations: false
              download_translations_args: >
                --language=de
              create_pull_request: true
            env:
              GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
              CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
              CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
    
  4. andrii-bodnar commented on Mar 6, 2025

    @andrii-bodnar
    Member

    @Paul-Weaver it's interesting 🤔
    Based on the mentioned issue, I thought it was not working this way

  5. Paul-Weaver commented on Mar 6, 2025

    @Paul-Weaver
    Author

    @Paul-Weaver it's interesting 🤔 Based on the mentioned issue, I thought it was not working this way

    I'm not sure, but you can see they don't have the correct permissions anyhow it's missing pull-requests: write

  6. andrii-bodnar commented on Mar 6, 2025

    @andrii-bodnar
    Member

    @Paul-Weaver indeed, that looks like a reason. So it seems to work without any changes to the action code.

    By the way, would you like to contribute a readme update describing how to authenticate via the GitHub app? It would be very helpful for other developers.

  7. Paul-Weaver commented on Mar 6, 2025

    @Paul-Weaver
    Author

    @Paul-Weaver indeed, that looks like a reason. So it seems to work without any changes to the action code.

    By the way, would you like to contribute a readme update describing how to authenticate via the GitHub app? It would be very helpful for other developers.

    Sure I can help with that!

  8. 3dbrows commented on May 28, 2025

    @3dbrows
    Contributor

    @Paul-Weaver Did you happen to have a chance to write such docs? I'm struggling to understand why this doesn't work:

    jobs:
      crowdin-test:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          pull-requests: read
    

    Here we see the job has readonly access.

    But suppose I supply a GitHub App access token that I am absolutely certain has contents: write and pull-requests: write on the relevant repo (assume I obtain one and store it in steps.generate-token.outputs.token):

    - name: Crowdin
      uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2
      with: [snip]
      env:
        GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
        CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
        CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
    

    I get a 403 on trying to write to the repo.

    My suspicion is that one cannot overwrite GITHUB_TOKEN like that, which @lol3909 noted here. I note that in your (Paul's) comment you have write access in the permissions block and I wonder if that's why it worked for you there?

    @andrii-bodnar Is there any possibility of being able to supply GITHUB_TOKEN as GH_TOKEN in the case where we want to override it in our jobs? That might achieve my desired behaviour, which is:

    • Start a job with very low permissions (readonly)
    • Make a call to obtain a write-access token
    • Pass that token to Crowdin (i.e. a "stepped-up" GITHUB_TOKEN)
  9. andrii-bodnar commented on Jun 2, 2025

    @andrii-bodnar
    Member

    Hi @3dbrows,
    In the related issue, there was a proof of concept for this. Could you please check to see if that solution works for you?

  10. 3dbrows commented on Jun 4, 2025

    @3dbrows
    Contributor

    Thanks very much @andrii-bodnar , I will try that and report back; I am wondering if this will work in light of what GitHub say here about restrictions on variable names:

    You can't overwrite the value of the default environment variables named GITHUB_* and RUNNER_*.

    It's not super clear to me from that whether overwrite is used in a strict sense, or it means you also can't write them with original values.

    So, it might object to GITHUB_APP_TOKEN but not to GH_TOKEN or similar, because the latter doesn't match GITHUB_*. We'll see!

  11. andrii-bodnar commented on Jun 4, 2025

    @andrii-bodnar
    Member

    @3dbrows I just merged your PR into the app_token branch. Could you please check it out?

  12. 3dbrows commented on Jun 6, 2025

    @3dbrows
    Contributor

    I've been struggling with this but I don't think it's any problem with the action; more what limitations GitHub Actions puts in place.

    I tried with the following, but it 403's every time. Interestingly, if I get a token locally and try pushing to a REPO_URL containing github.com rather than api.github.com, it works. I also tried using different GITHUB_ACTOR values in the REPO_URL, but it made no difference.

    I'm left with the suspicion that GitHub Actions does not let you escalate privileges by generating a token with broader permissions than what is given in the job's permissions stanza... but I also have big doubts about that, because how would the remote Git origin know?

    And yet, locally, using git push https://any-username:TOKEN@github.com/foo/bar.git does work. Just not in an Action. (Using api.github.com works in neither place.) For what it's worth, my token starts with ghs_ and I'm on GitHub Enterprise SaaS.

    At this point, I'm sorry to say, I'm out of ideas... so we might be forced to just use contents: write in the job permissions.

          - uses: actions/create-github-app-token@v2.0.6
            id: generate-token
            with:
              app-id: "<redacted>"
              private-key: ${{ secrets.APP_PRIVATE_KEY }}
              permission-contents: write
          - name: crowdin action
            uses: crowdin/github-action@app_token
            with:
              config: crowdin.yml
              github_api_base_url: github.com  # I also tried omitting this line
              upload_sources: false
              download_translations: true
            env:
              GH_TOKEN: ${{ steps.generate-token.outputs.token }}
              CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
              CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
    

    Thanks for looking at this with me!

  13. Paul-Weaver commented on Jun 6, 2025

    @Paul-Weaver
    Author

    @3dbrows here’s what worked for me:

    1. Two tokens, two roles
    • GITHUB_TOKEN (default) handles the REST / GraphQL API calls (open/update PR, set commit status).
    • GH_TOKEN (GitHub App token) handles the raw git push. If GITHUB_TOKEN (default token is read-only), the API call that opens the PR will 403—even though the push itself succeeded.
    1. Give the job write permissions
      Add “contents: write” and “pull-requests: write” to the job.
      This upgrades the default token just long enough for the API work while GH_TOKEN stays scoped for the push.

    2. Drop github_api_base_url : Let the action use its default endpoint (api.github.com) unless you’re on a self-hosted GHES instance.

    Fix

    Give the job write perms so the default token can open/update the PR:

    name: crowdin-sync
    
    on:
      workflow_dispatch:
    
    jobs:
      crowdin:
        runs-on: ubuntu-latest
        permissions:
          contents: write
          pull-requests: write
    
        steps:
          - uses: actions/checkout@v4
    
          - uses: actions/create-github-app-token@v2
            id: app-token
            with:
              app-id: ${{ vars.CROWDIN_APP_ID }}
              private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
              permission-contents: write
              permission-pull-requests: write   # keeps token scoped
    
          - name: Crowdin sync
            uses: crowdin/github-action@app_token
            with:
              config: crowdin.yml
              upload_sources: false
              download_translations: true
            env:
              GH_TOKEN: ${{ steps.app-token.outputs.token }}
              CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
              CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
    

    Give that a spin and shout if anything’s still off.

  14. Paul-Weaver commented on Jun 6, 2025

    @Paul-Weaver
    Author

    TL;DR — The Crowdin action needs two tokens: the default GITHUB_TOKEN for API calls (opening/updating the PR) and a scoped GH_TOKEN from your GitHub App for the actual git push. If the job’s permissions are read-only, those API calls 403 even though the push works. Give the job temporary contents: write and pull-requests: write perms so GITHUB_TOKEN can finish its work, keep GH_TOKEN for the push, and drop github_api_base_url unless you’re on GHES. That combo removes the 403.

  15. 3dbrows commented on Jun 9, 2025

    @3dbrows
    Contributor

    Thanks Paul. That does, indeed, work. I'd conclude from this that if the action were to use GH_TOKEN for everything, (though I thought it did) obtained from a correctly configured app (with permissions to write PRs and pushes), then the following would be possible.

    The main (only?) advantage being that it scores maximum points in terms of token scope reduction.

    name: crowdin-sync
    
    on:
      workflow_dispatch:
    
    jobs:
      crowdin:
        runs-on: ubuntu-latest
        permissions:
          contents: read # to allow the checkout to work
    
        steps:
          - uses: actions/checkout@v4
    
          - uses: actions/create-github-app-token@v2
            id: app-token
            with:
              app-id: ${{ vars.CROWDIN_APP_ID }}
              private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
              permission-contents: write
              permission-pull-requests: write
    
          - name: Crowdin sync
            uses: crowdin/github-action@app_token
            with:
              config: crowdin.yml
              upload_sources: false
              download_translations: true
            env:
              GH_TOKEN: ${{ steps.app-token.outputs.token }}
              CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
              CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
    
  16. andrii-bodnar commented on Jun 9, 2025

    @andrii-bodnar
    Member

    Great work on these findings, @Paul-Weaver and @3dbrows. Thank you! 🚀

  17. iorate commented on Mar 7, 2026

    @iorate

    Hi @andrii-bodnar,

    I believe this issue is not actually resolved (#305 is a duplicate of this).

    The fix — supporting GH_TOKEN to override GITHUB_TOKEN in push_to_branch() and create_pull_request() — exists in the app_token branch but has never been merged into master (compare). Users on any released version therefore cannot override the token.

    Could you merge app_token into master? Thank you!

  18. andrii-bodnar commented on Mar 12, 2026

    @andrii-bodnar
    Member

    Hi @iorate, thanks for reporting this! It's a very confusing situation, i'm sorry for this!

    I just rebased the app_token branch and created PR #307. Could you please quickly check to see if everything is working as expected before I merge and release it? Thank you!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions