aspnetcore: OAuth: The oauth state was invalid or missing
Describe the bug
I’m trying to set up OAuth with AdobeIO. I haven’t found any .NET libs around for it so I am trying to roll it myself.
It redirects me to Adobe just fine, but there’s something wrong with the callback because it always tells me the oauth state is invalid or missing.
Upon further investigation using a quick ISecureDataFormat
I saw that the callback initially does have the oauth state which gets unprotected successfully, but Unprotect gets called again immediately afterwards with no protected text.
To Reproduce
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services
.AddAuthentication()
.AddOAuth("adobe", options =>
{
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (HttpRequestMessage req, X509Certificate2 cert, X509Chain chain, SslPolicyErrors errors) => {
Console.WriteLine("HELLO");
return true;
};
var httpClient = new HttpClient(handler);
options.ClientId = Configuration["AdobeAuth:ClientId"];
options.ClientSecret = Configuration["AdobeAuth:ClientSecret"];
options.CallbackPath = new Microsoft.AspNetCore.Http.PathString(Configuration["AdobeAuth:CallbackPath"]);
options.AuthorizationEndpoint = Configuration["AdobeAuth:AuthorizationEndpoint"];
options.TokenEndpoint = Configuration["AdobeAuth:AuthorizationEndpoint"];
options.Backchannel = httpClient;
options.SaveTokens = false;
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Scope.Clear();
var scopes = Configuration["AdobeAuth:Scopes"].Split(',');
foreach(var scope in scopes)
{
options.Scope.Add(scope);
}
options.Events.OnAccessDenied += (context) => {
Console.WriteLine("access denied");
return Task.CompletedTask;
};
options.Events.OnTicketReceived += (context) => {
Console.WriteLine("ticket received");
return Task.CompletedTask;
};
options.Events.OnCreatingTicket += (context) => {
Console.WriteLine("creating ticket");
return Task.CompletedTask;
};
options.Events.OnRedirectToAuthorizationEndpoint += (RedirectContext<OAuthOptions> context) => {
Console.WriteLine("redirecting to auth endpoint");
return Task.CompletedTask;
};
options.Events.OnRemoteFailure += (context) => {
Console.WriteLine("remote failure");
return Task.CompletedTask;
};
options.StateDataFormat = new TestSecureDataFormat();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
public class TestSecureDataFormat : ISecureDataFormat<AuthenticationProperties>
{
private readonly IDataProtector protector;
public TestSecureDataFormat()
{
protector = DataProtectionProvider.Create("server").CreateProtector("authentication");
}
public string Protect(AuthenticationProperties data)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
var p = protector.Protect(json);
return p;
}
public string Protect(AuthenticationProperties data, string purpose)
{
return Protect(data);
}
[return: MaybeNull]
public AuthenticationProperties Unprotect(string protectedText)
{
var u = protector.Unprotect(protectedText);
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<AuthenticationProperties>(u);
return props;
}
[return: MaybeNull]
public AuthenticationProperties Unprotect(string protectedText, string purpose)
{
return Unprotect(protectedText);
}
}
[Route("{controller}")]
public class AuthController : Controller
{
private readonly IConfiguration configuration;
public AuthController(IConfiguration configuration)
{
this.configuration = configuration;
}
[HttpGet("login")]
public IActionResult Login()
{
var properties = new OAuthChallengeProperties()
{
RedirectUri = new PathString(this.configuration["AdobeAuth:CallbackPath"]),
};
return Challenge(properties, "adobe");
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>32de766e-0589-4a13-8b88-f04d4faa8c58</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
</ItemGroup>
</Project>
Please ignore the Console.WriteLines, I have been pulling my hair out trying to figure this out for a while 😪
I also have no idea why I need to override the server cert verification. Either my machine doesn’t trust adobe’s cert, or they don’t trust mine? I’m not sure.
Further technical details
- ASP.NET Core version: 5
- Include the output of
dotnet --info
:
dotnet --info
.NET SDK (reflecting any global.json):
Version: 5.0.202
Commit: db7cc87d51
Runtime Environment:
OS Name: Windows
OS Version: 10.0.19042
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\5.0.202\
Host (useful for support):
Version: 5.0.5
Commit: 2f740adc14
.NET SDKs installed:
2.1.523 [C:\Program Files\dotnet\sdk]
3.1.201 [C:\Program Files\dotnet\sdk]
3.1.408 [C:\Program Files\dotnet\sdk]
5.0.103 [C:\Program Files\dotnet\sdk]
5.0.202 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
- The IDE (VS / VS Code/ VS4Mac) you’re running on, and its version
Version: 1.55.2 (system setup)
Commit: 3c4e3df9e89829dce27b7b5c24508306b151f30d
Date: 2021-04-13T09:35:57.887Z
Electron: 11.3.0
Chrome: 87.0.4280.141
Node.js: 12.18.3
V8: 8.7.220.31-electron.0
OS: Windows_NT x64 10.0.19042
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 18 (9 by maintainers)
Turns out the redirect was happening because the TokenEndpoint is
https://ims-na1.adobelogin.com/ims/token
nothttps://ims-na1.adobelogin.com/ims/authorize/v2
🙃I was able to run this locally and found the issue. The app receives the callback: GET https://localhost:5001/signin-adobe?code=...&state=… It sends a request on the backchannel HttpClient to redeme the code for a token. This fails and returns a 302 redirect to
Location: https://localhost:5001/signin-adobe?error=invalid_scope
. The HttpClient automatically follows the redirect, making a new request back to your own server and signin path, but without the state.I’m not sure why they thought a redirect was an appropriate way to report errors from the token endpoint. It does explain why you needed to add ServerCertificateCustomValidationCallback in startup, HttpClient didn’t trust your local development certificate for the redirected request (that part works for me, I trusted the dev cert).
I was able to supress the redirect like this:
To see the real error message:
I’ll let you sort out what’s wrong with the scope.
What CallbackPath did you use? It should be a unique value like “/signin-adobe”, it must not overlap with a controller route. OAuthChallengeProperties.RedirectUri should provide a different value than the CallbackPath for the middleware to redirect to after the CallbackPath.
When you get it working, consider contributing to https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers