Repository navigation
OCI Authentication Hardcoded for Azure #1946
Description
Prerequisites
- Write a descriptive title.
- Make sure you are able to repro it on the latest released version
- Search the existing issues.
Steps to reproduce
The authentication mechanisms used on OCI/Container Registries is essentially hardcoded to only work for ACR based registries. This makes it impossible to use PSResourceGet with other OCI registries that don't follow the ACR specific auth flow.
For example it is not possible to install from GitHub's Container/OCI repository. I've got a test nupkg published at https://github.com/jborean93/PowerShell-PublishTest/pkgs/container/publishtest and this is what happens when you try to install it with and without a credential with PSResourceGet 1.2.0-rc3
Register-PSResourceRepository -Name GHCR -Uri https://ghcr.io -ApiVersion ContainerRegistry
# Try it with anonymous access/no credential
Install-PSResource -Name jborean93/publishtest -Repository GHCR -Debug -Verbose -TrustRepository
$Error[1] | Select-Object *DEBUG: Package version parsed as NuGet version: '1.2.0-rc3'
DEBUG: Reading package metadata from: '/home/jborean/.local/share/powershell/Modules/Microsoft.PowerShell.PSResourceGet/1.2.0/PSGetModuleInfo.xml'
DEBUG: In InstallPSResource::ProcessInstallHelper()
DEBUG: In InstallHelper::BeginInstallPackages()
DEBUG: Parameters passed in >>> Name: 'jborean93/publishtest'; VersionRange: ''; NuGetVersion: ''; VersionType: 'NoVersion'; Version: ''; Prerelease: 'False'; Repository: 'GHCR'; AcceptLicense: 'False'; Quiet: 'False'; Reinstall: 'False'; TrustRepository: 'True'; NoClobber: 'False'; AsNupkg: 'False'; IncludeXml 'True'; SavePackage 'False'; TemporaryPath ''; SkipDependencyCheck: 'False'; AuthenticodeCheck: 'False'; PathsToInstallPkg: '/home/jborean/.local/share/powershell/Modules,/home/jborean/.local/share/powershell/Scripts'; Scope 'CurrentUser'
DEBUG: In InstallHelper::ProcessRepositories()
VERBOSE: Setting Secret Management network credentials
VERBOSE: Attempting to search for packages in 'GHCR'
DEBUG: In InstallHelper::InstallPackages()
DEBUG: In InstallHelper::InstallPackage()
DEBUG: In ContainerRegistryServerAPICalls::FindName()
DEBUG: In ContainerRegistryServerAPICalls::FindPackagesWithVersionHelper()
DEBUG: In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()
DEBUG: In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()
DEBUG: Is repository unauthenticated: False
DEBUG: In ContainerRegistryServerAPICalls::GetContainerRegistryRefreshToken()
DEBUG: In ContainerRegistryServerAPICalls::GetHttpResponseJObjectUsingContentHeaders()
Install-PSResource: Response returned error with status code MethodNotAllowed: Method Not Allowed.
VERBOSE: Attempting to delete '/tmp/a36c194f-1a8b-4239-8ec3-ae8c0e2f5038'
VERBOSE: Successfully deleted '/tmp/a36c194f-1a8b-4239-8ec3-ae8c0e2f5038'
Install-PSResource: Package(s) 'jborean93/publishtest' could not be installed from repository 'GHCR'.
# $Error[1]
PSMessageDetails :
Exception : System.Exception: Response returned error with status code MethodNotAllowed: Method Not Allowed.
at Microsoft.PowerShell.PSResourceGet.ContainerRegistryServerAPICalls.SendRequestAsync(HttpRequestMessage
message) in C:\__w\1\s\PSResourceGet\src\code\ContainerRegistryServerAPICalls.cs:line 1170
at Microsoft.PowerShell.PSResourceGet.ContainerRegistryServerAPICalls.GetHttpResponseJObjectUsingContentHead
ers(String url, HttpMethod method, String content, Collection`1 contentHeaders, ErrorRecord& errRecord) in
C:\__w\1\s\PSResourceGet\src\code\ContainerRegistryServerAPICalls.cs:line 1045
TargetObject : Microsoft.PowerShell.PSResourceGet.Cmdlets.InstallPSResource
CategoryInfo : InvalidResult: (Microsoft.PowerShel…s.InstallPSResource:InstallPSResource) [Install-PSResource], Exception
FullyQualifiedErrorId : HttpRequestCallFailure,Microsoft.PowerShell.PSResourceGet.Cmdlets.InstallPSResource
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {0, 1}
The same error occurs when you use a -Credential with a GitHub username/PAT. The error may vary depending on whether there is an Az context available as PSResourceGet falls back on this for all registries.
The reason is because GetContainerRegistryAccessToken is coded to do the following
- Use a cached token from a previous request (not applicable here)
- Retrieve the Azure AccessToken and TenantId from the SecretStore if a credential is set on the registered repository (not applicable here)
- Checks if the registry allows unauthenticated requests (false here)
- The logic for getting an anonymous token is also ACR specific and won't work for other OCI repositories
- Fallback to getting the Az Access Token from
DefaultAzureCredential- If none available it will fail here
- In my case I do have an az token available
- If we have an Az AccessToken from SecureStore or the environment use an ACR OAuth2 flow to get the ACR bearer token
- This auth flow is ACR specific and fails with the error you see above
- If not AccessToken then it fails
The 3 problems I see here
- The anonymous token workflow needs to be updated to work generically
- The
oauth2/exchangeworkflow that ultimately gets the ACR bearer token from an Az AccessToken needs to be selectively done (some flag on the registered repository maybe?) - If a credential is passed in then the
oauth2/exchangeflow should not be done unless- The incoming credential is an Az AccessToken from something like
Get-AzAccessToken - Not if its an Azure client id and secret as that is just used directly in the token exchange
- The incoming credential is an Az AccessToken from something like
I'm hoping to send through a PR to try and fix this at least for some of the scenarios but wanted to raise the issue to at least summarise what is happening. I've written Get-OciBearerToken to show the workflow that I've found works for ACR, MAR, GHCR, and hopefully any other OCI compliant registry. I've used this function to
- push a new package for ACR, GHCR
- GHCR uses the GitHub username and PAT with
write:packagesset or through the GHAGITHUB_TOKENvalue - ACR can use an app client_id and secret value directly
- ACR can use the AccessToken returned by
Get-AzAccessTokenfor any valid connection using theoauth2/exchangeto get the ACR refresh token
- GHCR uses the GitHub username and PAT with
- pull from ACR, MAR, GHDR
- Using anonymous access
- With the same credential as push
The biggest hiccup is figuring out whether we have an Az AccessToken to do the oauth2/exchange step.
Expected behavior
Module can be installed with no credential when the registry allows it or with a credential when it is private.Actual behavior
See above (fails)Error details
See aboveEnvironment data
ModuleType Version PreRelease Name ExportedCommands
---------- ------- ---------- ---- ----------------
Binary 1.2.0 rc3 Microsoft.PowerShell.PSResourceGet {Compress-PSResource, Find-PSResource, Get-InstalledPSResource, G…
Name Value
---- -----
PSVersion 7.5.1
PSEdition Core
GitCommitId 7.5.1
OS Fedora Linux 43 (Server Edition)
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0Visuals
No response
Activity
jborean93 commentedon Feb 20, 2026
This also brings into question around what the credential type is expected to be in a SecretManagement store. Right now it expects a credential in the form of
tenantIdand the Az AccessToken and will do theoauth2/exchangeflow to get the ACR refresh token from that. This isn't ideal asIt seems like this logic should change so that the credential stored here is used directly with the bearer token exchange like
Maybe a credential can have additional metadata to indicate that it's an AzAccess token so it does that extra
oauth2/exchangestep if that's needed for testing but that should definitely not be the default.jborean93 commentedon Feb 22, 2026
I've opened #1949 and created a summary on the changes/risks/reasons behind it all. I'm happy to talk about it further as needed but hopefully this helps explain what is happening and why I made the changes I did.
Note: This has been generated by AI but I've reviewed/modified the changes as necessary.
OCI Registry Authentication: Current Issues and Proposed Changes
Summary
PSResourceGet's current authentication implementation is tightly coupled to Azure Container Registry (ACR) and doesn't work with other OCI-compliant registries like GitHub Container Registry (GHCR). This document outlines:
*.azurecr.ioto detect ACR endpoints for automatic Azure loginGlossary
Registries
myregistry.azurecr.io)ghcr.io)mcr.microsoft.com)Token Types (Authentication Flow)
Understanding the token types used and their names is critical to understanding the problems:
Azure Access Token - Short-lived token from Azure login (
az login,Connect-AzAccount,.NET DefaultAzureCredential)ACR Refresh Token - Short-lived token ACR issues when given an Azure Access Token
/oauth2/exchange)Registry Bearer Token - The actual token used for OCI API requests
Token Scope - Defines what the bearer token can do
{resourcetype}:{packagename}:{action}repository:mymodule:pull,registry:catalog:*Current Authentication Workflow
The current implementation attempts authentication in the following order:
Step 1: Discover Authentication Requirements
Send
HEAD /v2/to the registry and check the response:realmandservicefromWWW-AuthenticateheaderExample
WWW-Authenticateheaders:Step 2: Try Anonymous Access
Request a bearer token anonymously from
{realm}?service={service}&scope={scope}with noAuthorizationheader.Scope used:
repository:*:*for package operationsregistry:catalog:*for catalog/search operationsIf this succeeds, use the returned bearer token from the
access_tokenkey for API requests.Step 3: Try Authenticated Access (ACR-Specific)
If anonymous access fails:
Register-PSResourceRepositoryOR attempt to get Azure Access Token viaDefaultAzureCredential(environment/browser login)Key Limitation: This workflow assumes all explicit credentials are Azure Access Tokens and always uses the ACR OAuth2 flow.
Problems with Current Implementation
The current implementation works for ACR and unauthenticated registries (MCR) but fails with other OCI registries (GHCR, Docker Hub, etc.). Issues fall into three categories:
1. OCI Spec Compatibility Issues
Problem: Using
HEAD /v2/instead ofGET /v2/The OCI spec recommends
GET /v2/for the version check endpoint. Some registries don't supportHEAD:Impact: PSResourceGet fails immediately when trying to use GHCR.
Problem: Overly Broad Token Scopes
Current implementation requests wildcard scopes (
repository:*:*) which many registries reject:Impact: Anonymous and authenticated access fails even for public packages on GHCR.
Problem: Anonymous token is only checked under access_token
Current implementation only uses the
access_tokenkey in theGET /v2/response but OCI registries can return it on eithertokenoraccess_tokenhttps://distribution.github.io/distribution/spec/auth/token/#token-response-fields.2. ACR-Specific Assumptions
Problem: All Explicit Credentials Use ACR OAuth2 Flow
The current code assumes any provided credential is an Azure Access Token and always performs the ACR-specific OAuth2 exchange. This makes it impossible to use:
Impact: Cannot authenticate using longer lived Entra client secret or to non-ACR registries with credentials.
Problem: Azure Login Attempted for All Registries
When no explicit credential is provided, the code attempts to retrieve an Azure Access Token via
DefaultAzureCredential(environment variables or browser login) regardless of registry type. This is made worse when there is no credential present and the browser cannot be displayed (VM, SSH logon, etc) as an error will be displayed.Impact: Users get unnecessary Azure login prompts when installing from GHCR or other non-ACR registries.
3. ACR Credential Limitations
Problem: Cannot Use Service Principals Directly
ACR supports authentication with Entra Service Principals (client ID + secret), which are suitable for automation and long-lived scenarios. However, the current implementation can only use short-lived Azure Access Tokens.
Impact: Users cannot create stable, long-lived credentials for ACR in automation scenarios.
Proposed Changes
This PR implements standard OCI authentication with ACR-specific handling only when needed. Here's how each change addresses the problems:
1. Fix OCI Compatibility Issues
GET /v2/instead ofHEAD /v2/repository:{packagename}:pullfor installrepository:{packagename}:push,pullfor publishregistry:catalog:*only for catalog operationstokenoraccess_tokenresponsetokenoraccess_tokenin the bearer token response exchange2. Implement Standard OCI Authentication
New Authentication Flow:
GET /v2/(changed fromHEAD)-Credentialparameter support onInstall-PSResource/Publish-PSResource):AzureAccessToken: GUID, use ACR OAuth2 flowHttpClientHandler.Credentialwhich should work butclientId:clientSecretcombo (no access token).azurecr.io:DefaultAzureCredential(environment/browser login)3. Key Behavioral Changes
Now Supported:
-CredentialparameterBreaking Change:
AzureAccessToken: GUIDACR Auto-Login:
servicefromWWW-Authenticateends with.azurecr.ioBreaking Changes
Change: Credential Handling for ACR
Before (PSResourceGet 1.1.1+):
After (This PR):
Impact Assessment
Who is affected:
Register-PSResourceRepositoryfor ACRLikelihood of impact:
DefaultAzureCredential(auto-login) rather than explicit tokensMigration path:
AzureAccessToken: GUIDusername prefix whereGUIDis thetenantId, orWhy This Change is Beneficial
Potential Risks and Concerns
Primary Risk: ACR Detection via Service Name Pattern
The Issue:
The PR uses the
servicevalue from theWWW-Authenticateheader to detect ACR registries:Code checks:
if ($service -like "*.azurecr.io")→ trigger Azure auto-loginWhy This Matters:
This pattern-matching is critical because we cannot trigger
DefaultAzureCredential(browser login prompts) for non-ACR registries. The code must distinguish between ACR and other OCI registries.The Risk:
If Azure changes the
servicevalue format in the future (unlikely but possible), the detection would break and:Likelihood: Low - Service names are typically tied to DNS and ACR architecture
Mitigation: The behavior degrades gracefully - explicit credentials still work
Alternative Approaches Considered
Option 1: Explicit ACR Flag (More Conservative)
Add a switch to
Register-PSResourceRepositoryto explicitly mark ACR registries:Pros:
Cons:
Option 2: New ApiVersion for OCI Standards
Add
-ApiVersion OciRegistrywhile keepingContainerRegistryfor legacy ACR behavior:Pros:
Cons:
Option 3: Metadata-Based Credential Typing
Use
SecretManagementmetadata to indicate credential type:Pros:
Cons:
Recommended Approach (Current PR)
Why the pattern-matching approach is best:
The service name pattern is stable (tied to Azure's DNS infrastructure) and the risk is acceptable given the benefits.
Summary
This PR transforms PSResourceGet from an ACR-centric implementation to a proper OCI-compliant registry client while maintaining ACR-specific features where needed.
What works now that didn't before:
-CredentialparameterWhat continues to work:
The tradeoff:
SydneyhSmith commentedon Aug 13, 2026
We are working on getting the ORAS .net library merged in which hopefully should resolve some of these issue