Skip to main content
Use this pattern when you give your users direct Git access. Your users clone and push with a remote on your domain, such as https://git.your-app.com/team/project-alpha.git. Your server authenticates each request with your own system and forwards it to Code Storage with a short-lived token. Your users never receive a Code Storage token or your private key. You control:
  • Authentication. Use your own sign-in, access tokens, or SSO.
  • Authorization. Decide which user can read or write each repository, and set ref policies for each user.
  • Revocation. When you remove access in your system, the next request fails.
  • Access logs. Record the user, IP address, repository, and operation of each request.

Choose an approach

A proxy gives you the most control, but it is also the most work: you run a server in the path of every clone and push. Two lighter options do not need a proxy:
  • Short-lived remote URLs. Your backend gives each user a remote URL with a short-lived token. See repo.getRemoteURL().
  • A Git credential helper. You ship a credential helper that gets a short-lived token from your API each time Git needs credentials.
With these options, your users hold a Code Storage token until it expires, and their requests go directly to Code Storage. Use a proxy when your users must never hold a token, or when you must check, revoke, and log each request.

How it works

Your proxy must:
  • Forward only the Git endpoints below, and return 404 for every other path.
  • Return 401 when the user’s credentials are missing or incorrect, and 404 when the user cannot access the repository.
  • Sign a new token for each request, with the repository, the scopes that the request needs, your user ID as sub, and your ref policies.
  • Never forward the user’s Authorization header to Code Storage.
  • Stream the request body and the response body.

The endpoints that Git uses

Git over HTTPS uses four requests for each repository, under /{repo}.git/:
The example proxy does not support Git LFS yet. It returns 404 for LFS requests.

Build the proxy

The proxy is a Fetch API handler: it takes a Request and returns a Response. This example runs it on Node.js with srvx and signs tokens with jose. Install the packages:
Set PIERRE_ORG and PIERRE_PRIVATE_KEY as shown in Authentication. Each step below adds code to proxy.ts, in order. The complete file is at the end of this section.

Set up

proxy.ts

Match the Git endpoints

Each route sets the access that its endpoint needs. A request that matches no route gets 404.
proxy.ts

Check the user

The handler authenticates the user, checks access to the repository, and writes one log line for each request. The line has the user, IP address, repository, endpoint, and response status.
proxy.ts

Connect your user system

Put your own logic in auth.ts. It exports two functions:
  • authenticate(request) returns the user, as an object with an id, or null. Git sends the user’s credentials as HTTP Basic auth in the Authorization header. For null, the proxy returns 401, and Git asks the user or its credential helper for credentials.
  • authorize(user, repo, access) returns true if the user can access the repository. access is 'read' for a clone or fetch, and 'write' for a push. For false, the proxy returns 404, so a user cannot find out which repositories exist.

Sign a token

The token covers one repository and the scopes that the request needs. It sets sub to your user ID, which Code Storage shows in its logs. The token expires after 300 seconds. It only needs to be valid when Code Storage receives the request, so a long push still completes.
proxy.ts
If you use a restricted key, its allowlist must include git:read and git:write. A restricted key also limits the token lifetime to 3,600 seconds.

Forward the request

The proxy forwards only the headers that Git needs. It does not forward the user’s Authorization header. A clone or push can transfer a large pack, so the proxy streams both bodies and does not buffer them.
proxy.ts

Serve the proxy

proxy.ts
handleGit() uses only the Fetch API. To run it on another server, pass it each Request and the client IP address, and return its Response. Run the proxy with Node.js 24 or later. Node.js runs TypeScript files directly. Set "type": "module" in package.json.

The complete proxy

proxy.ts

Use the remote

Give your users the URL of your proxy. Git sends their credentials with HTTP Basic auth, so they can use a Git credential helper as they do with any other host:
When Git asks for credentials, the user enters the credentials that your authenticate() accepts, for example a username and an access token from your product. The proxy matches repository paths that end in .git, so give your users URLs in this form.

Attribute pushes

Push webhooks identify the repository and ref, but not the user who pushed. Your proxy log identifies the user for each git-receive-pack request. Use your proxy log as the record of who pushed.

Test the proxy

Run these checks before you give the remote to users:
  1. Clone a repository through the proxy with a valid user.
  2. Push to a branch that the user can write.
  3. Clone with an incorrect token. The proxy returns 401.
  4. Clone a repository that the user cannot access. The proxy returns 404.
  5. Push to a ref that a ref policy blocks. Git shows push denied by policy.