I have an IdentityServer setup with an API and a SPA written with React. The SPA is using the javascript oidc client library to authenticate with the IdentityServer and then get data from the API. The SPA and API are in the same project, the SPA is served using services.AddSpaStaticFiles and app.UseSpa so I thought I should be able to use both authentication schemes interchangeably.
The problem is I have images stored on the API side that I want the SPA client to be able to get and place within an < img > tag, with the option to click on it and open the full size image in a new window. The images have to require the user to be authenticated to access them though.
I've tried adding Cookie based authentication to the API's ConfigureServices, hoping that having the user authenticated on the SPA and then visiting the image URL on the API would work.
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddIdentityServerAuthentication("apiAuth", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
})
.AddCookie("cookieAuth")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "afx_api";
options.SaveTokens = true;
});
And then adding [Authorize(AuthenticationSchemes = "cookieAuth")] on the Controller that'll return an image, and [Authorize(AuthenticationSchemes = "apiAuth")] on all the other API controllers.
However when I try visiting an image, like lets say http://localhost:6000/api/file/1 I get redirected here http://localhost:6001/Account/Login?ReturnUrl=%2Fapi%2Fdocuments%2Ffile%2F90404 even though I've been authenticated and the normal API calls work.
How would I go about doing this? Thanks
EDIT: More code from my setup
IdentityServer/Config.cs client config
new Client
{
ClientId = "client",
ClientName = "React Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AccessTokenLifetime = 3600,
RedirectUris = {
"http://localhost:6001/callback",
"http://localhost:6001/silent_renew.html",
},
PostLogoutRedirectUris =
{
"http://localhost:6001/",
},
AllowedCorsOrigins =
{
"http://localhost:6001",
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
},
AlwaysIncludeUserClaimsInIdToken = true
}
ClientApp/src/userManager.js
import { createUserManager } from 'redux-oidc';
const settings = {
client_id: 'client',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
response_type: 'id_token token',
scope:"openid profile api",
authority: 'http://localhost:5000',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
loadUserInfo: true,
monitorSession: true
};
const userManager = createUserManager(settings);
export default userManager;
New startup based on Elrashid's answer:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookies",
options => options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "cookies")
.AddJwtBearer("jwt", options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api";
options.RequireHttpsMetadata = false;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "client";
options.SaveTokens = true;
options.ResponseType = "id_token token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api");
options.Scope.Add("offline_access");
options.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? "jwt" : "oidc";
});
Still getting the same result, which is a redirect to /Account/Login?ReturnUrl=%2Fimages%2Ffile%2F90404 when I try to get an image from the controller. The API still works. The controller method returns a PNG and without authorization required it works.
[Authorize(AuthenticationSchemes = "cookies")]
[Route("[controller]")]
public class ImagesController : Controller
{
...
}