Following some Microsoft samples, I got to this point:
ASP.NET Core setup:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAD:ClientId"],
Authority = Configuration["Authentication:AzureAd:Authority"],
ResponseType = OpenIdConnectResponseType.IdToken,
AutomaticAuthenticate = true,
TokenValidationParameters = new TokenValidationParameters()
});
AuthorizationTest endpoint:
[HttpGet]
[Authorize]
public IActionResult Get()
{
return Ok("SAMPLE TEXT - if you can read this then call it a day :)");
}
Client:
try
{
var result = await authContext.AcquireTokenAsync(WebApiResourceId, WebApiClientId, WebApiRedirectUri, new PlatformParameters(PromptBehavior.Auto));
authorizedClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var authorizedMessage = await authorizedClient.GetAsync("/AuthorizationTest");
var statusCode = authorizedMessage.StatusCode.ToString();
var message = await authorizedMessage.Content.ReadAsStringAsync();
webBrowser.NavigateToString(message);
}
And the authorizedClient is initiated as:
private static HttpClientHandler handler = new HttpClientHandler
{
AllowAutoRedirect = true,
CookieContainer = new CookieContainer(),
UseCookies = true
};
private static HttpClient authorizedClient = new HttpClient(handler, false) { BaseAddress = WebApiBaseUri };
I used to initialize it only with the BaseAddress, and later added the handler following an answer here on So.
The problem:
Even though I get the token from AAD correctly, the response from the WEB API endpoint is an HTML (after an auto-redirect) that is the MS login page with the error "Your browser is set to block cookies....."
What should I change to make the HttpClient work? Or can I change the WebApi configuration to not use cookies? For the latter option I couldn't find any other alternative.
As discussed in the comments, you need to use the JWT bearer token middleware from the package Microsoft.AspNetCore.Authentication.JwtBearer.
The Open ID Connect middleware is designed to redirect a user to a sign in page, not for authenticating access tokens. An example usage of the JWT bearer token middleware can be found here: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore/blob/master/TodoListService/Startup.cs.
Take a look at this thread: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/514 - it is showing the scenario you are trying to achieve.
Related
I wrote an ASP.NET Core 3.1 API which uses the JWTBearer authentication system. This system works well when I call the API from Postman, but I can't figure out how to call it throught my own application or ASP.NET Core 3.1 MVC Website. Here is the configuration of the API :
API Configuration :
In the Startup.cs ConfigrationServices method I added this classical piece of code :
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
};
});
Then I added the middleware app.UseAuthentication(); to the Configure method.
Now I have a UsersController.cs with a SignIn method which returns a JWT as string if the credentials are corrects.
Finally, I added a simple GetUsers() method with an [Authorize] tag to test the JWT authentication as following :
// GET: api/Users
[Authorize(Roles = "Administrator")]
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
Throught Postman, everything works fine. I call the api/Users/SignIn url in POST passing my credentials as JSON. I get back my token in response with the 200 StatusCode.
Then I call the api/Users in GET passing the JWT previously obtained to the Postman settings Authorization > Type : Bearer Token. My API returns a successful code with all the data I asked for. Everything works as expected at the API side.
MVC Website Configuration :
To simplify the discussion with the API, I wrote a Class Library with a ClientService.cs class.
Int the ClientService.cs, I have this simplified piece of code which successfully gets the data from the API :
public async Task<string> GetPage(string model)
{
var request =
new HttpRequestMessage(
HttpMethod.Get,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
If I use it, let's say to get the informations from api/something as following : var smth = await GetPage("something") or any other AllowAnonymous method, it will properly works.
But if I want to make it working with an Authorize method, I actually add this piece of code just before sending the request :
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
Where the token variable contains my JWT hardwritten in code for testing purpose. Everything works fine too.
So now I'm obviously trying to avoid hardwritting the JWT. I decided to store it on the client side in an HttpOnly Cookie coupled with the AntiForgeryToken native function from ASP.NET Core. I wrote this code to store the cookie :
private void Authenticate(string token)
{
Response.Cookies.Append(
"JWT",
token,
new CookieOptions()
{
HttpOnly = true,
Secure = true,
}
);
}
And now I'm stucked here. Because I use it as a Service, my ClientService is the same for all my users. So I can't store the token somewhere and pass it to the newly created client for each request.
I tried to add the JWT to the header before calling the ClientService as following :
public async Task<ActionResult> Index()
{
Request.Headers.Add("Authorization", "Bearer " + Request.Cookies["JWT"]);
var users = await ClientService.GetUsersAsync();
//GetUsersAsync() simply call GetPage("users") method and deserialize the JSON returned as a List<User>
return View(users);
}
But because my ClientService create its own client which sends its own request each time I ask for some data from the API with this code :
var request =
new HttpRequestMessage(
HttpMethod.Get,,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
The headers I added before aren't passed to the API.
A simple solution could be to rewrite all my ClientService methods to accept a Token parameter but it seems redundant and painful.
Which is the best and simpliest solution to pass the token to my API ?
The case you are describing is actually Cookie Authentication :
1. Sign in
ClaimsIdentity claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
//Not mandatory: you can add the token to claim for future usage, like API request from server to server
claimsIdentity.AddClaim(new Claim("token", tokenId));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
2. Add Cookie authentication
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
};
}).AddCookieAuthentication();
AddCookieAuthentication looks like:
public static AuthenticationBuilder AddCookieAuthentication(this IServiceCollection services)
{
var authBuilder = services.AddAuthentication(sharedOptions =>
{
sharedOptions.RequireAuthenticatedSignIn = false;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts =>
{
opts.CookieManager = new ChunkingCookieManager();
opts.Cookie = new CookieBuilder()
{
Domain = "CookieDomain",
Name = "CookieName",
Path = "CookiePath",
SecurePolicy = CookieSecurePolicy.Always,
HttpOnly = true,
SameSite = SameSiteMode.Lax,
};
opts.ExpireTimeSpan = TimeSpan.FromMinutes(20);
opts.ForwardDefaultSelector = ctx =>
{
var authHeader = ctx.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith(JwtBearerDefaults.AuthenticationScheme) == true)
{
return JwtBearerDefaults.AuthenticationScheme;
}
else
{
return CookieAuthenticationDefaults.AuthenticationScheme;
}
};
});
return authBuilder;
}
A browser through which the user signed-in will receive the cookie cookie created in SignIn method and will be able to populate request via client as well.
Ok I figured it out myself, here is how I solved the problem :
1) On Startup.cs, add an HttpContextAccessor :
The Context from our ASP.NET Core website is only accessible by the controllers. The Class libraries can't access it.
To make it available to a class library, we need to use an HttPContextAccessor. It's a service we can use as a dependancy injection to our class library. We just have to add
services.AddHttpContextAccessor();
In the ConfigureServices method from the Startup.cs.
2) Adapt the Class Library
The ClientService needs to be able to receive the Dependancy Injection as following :
public class ClientService : IClientService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IHttpContextAccessor _contextAccessor;
private readonly string _baseAddress = $#"https://localhost:[PORT]/api/";
public ClientService(IHttpClientFactory clientFactory, IHttpContextAccessor contextAccessor)
{
_clientFactory = clientFactory;
_contextAccessor = contextAccessor;
}
}
Now we can access the current context from the HttpContextAccessor _contextAccessor variable.
Next we have to modify our GetPage(string model) method :
public async Task<string> GetPage(string model)
{
var request =
new HttpRequestMessage(
HttpMethod.Get,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
var authorization = _contextAccessor.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == "Authorization").Value.FirstOrDefault();
if(authorization != null)
{
client.DefaultRequestHeaders.Add("Authorization", authorization);
}
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
The code added will try to get the "Authorization" header from the request and if it's not null, we add it to our own request. This allows the request to the API with the JWT.
3) Add a middleware to add the JWT to each request
Now the final step is to add the JWT to each request from our ASP.NET Core application. If needed, the JWT will be consumed by our Class Library without changing any piece of code, either from the library or from the application.
In order to realise that, we have to add a middleware to the Startup.cs > Configure() method.
The middleware looks like that :
// Add header:
app.Use((context, next) =>
{
var jwt = context.Request.Cookies["JWT"];
if(jwt != null)
{
context.Request.Headers.Add("Authorization", "Bearer " + jwt);
}
return next.Invoke();
});
This just reads the Cookie from the request headers and if the JWT HttpOnly Cookie isn't null, we add the token to our request. This request will then be captured by our Class Library in order to use it to talk with the API.
A few things to note :
Adding the JWT to each request even if we don't need it could consume more bandwidth overtime. It could be also less safe (I'm not a security expert)
Adding the JWT to our main request, then create a local client with its own request in our class library seems not optimal. But it works as expected and I'm actually unaware of how to optimize it.
Feel free to point any issue in my solution or modify it to add some clarifications or even other more efficient ways to achieve it.
Here Angularjs is front end and Web API is middle tier. we are using AzureAD OpenID connect for Authentication.
I'm facing following issue. because of the my landing page is not loading
Access to XMLHttpRequest at 'https://login.microsoftonline.com/xx-86f1-41af-91ab-xxx/oauth2/authorize?client_id=xxxx1&response_mode=form_post&response_type=code%20id_token&scope=openid%20profile&state=OpenIdConnect.AuthenticationPropertiesxxxxx&noncexxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44300%2F&x-client-SKU=ID_NET451&x-client-ver=5.2.1.0' (redirected from 'https%3A%2F%2Flocalhost%3A44300%2F/api/Scorecard/GetServiceNameWithManagers?loginUser=xxx#microsoft.com') from origin 'https%3A%2F%2Flocalhost%3A44300%2F' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I've done lot of research and applied Access-Control-Allow-Origin =* at Request and response. also applied app.UseCors(Owin.Cors.CorsOptions.AllowAll);
but so far no success.
consider following code, AuthorizationCodeReceived delegate is not invoking very first time even though the user is logged in to microsoft site.
Please be noted, this code is not working very first time. It will work after few button clicks (postbacks) and then after few minutes if we run the application it's throws CORS preflight issue. Please help.
This is my startup.cs
public void Configuration(IAppBuilder app)
{
app.UseCors(Owin.Cors.CorsOptions.AllowAll);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new SystemWebChunkingCookieManager(),
});
//// Bearer token authentication middleware.(Ex: request from web clients,ajax calls able to pass authenticated bearer info)
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
TokenReplayCache = new TokenReplayCache(new MemoryCacheProvider())
},
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = ctx =>
{
//// Retrieve user roles from the request.
var authenticationTicket = ctx.Ticket;
if (authenticationTicket.Identity.IsAuthenticated)
{
////Use the block when role/user specific authorization needs and to modify the user identity claims based on requirement
}
return Task.FromResult(0);
},
OnRequestToken = ctx => { return Task.FromResult(0); }
}
});
//// Non Bearer authentication middleware. (Ex: request secured web api call directly from URL/Web API server scope it self)
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
ClientSecret = ConfigurationManager.AppSettings["ida:AppKey"],
Authority = Authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
AuthenticationMode = AuthenticationMode.Active,
ResponseType = "code id_token",
CallbackPath = new PathString("/"),
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = context =>
{
if (context.AuthenticationTicket.Identity.IsAuthenticated)
{
////Use the block when role/user specific authorization needs and to modify the user identity claims based on requirement
}
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(ClientId, Models.ConfigurationData.GraphSecret);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, GraphResource);
},
RedirectToIdentityProvider = context =>
{
if (context.ProtocolMessage.RedirectUri == null)
{
////To set the reply/redirect Url based on the request host environment.
////Hosting env details we get only through the owin context in startup and this is the delegate to set reply URL in OWincontext before the authentication.
string ReplyAddress = context.Request.Scheme + "://" + context.Request.Host + "/";
context.ProtocolMessage.RedirectUri = ReplyAddress;
}
//context.OwinContext.Authentication.User.Identity.IsAuthenticated = true;
if (context.OwinContext.Authentication.User.Identity.IsAuthenticated && context.ProtocolMessage.RequestType != IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout)
{
////To avoid infinite loop of redirections in request if user is authenticated and unauthorized.
context.HandleResponse();
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return Task.FromResult(0);
}
},
TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles",
TokenReplayCache = new TokenReplayCache(new MemoryCacheProvider())
},
});
System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = System.IdentityModel.Claims.ClaimTypes.NameIdentifier;
}
Access to XMLHttpRequest at 'https://login.microsoftonline.com/xx-86f1-41af-91ab-xxx/oauth2/authorize?client_id=xxxx1&response_mode=form_post&response_type=code%20id_token&scope=openid%20profile&state=OpenIdConnect.AuthenticationPropertiesxxxxx&noncexxxxx&redirect_uri=https%3A%2F%2Flocalhost%3A44300%2F&x-client-SKU=ID_NET451&x-client-ver=5.2.1.0' (redirected from 'https%3A%2F%2Flocalhost%3A44300%2F/api/Scorecard/GetServiceNamxxxManagers?loginUser=xxx#microsoft.com') from origin 'https%3A%2F%2Flocalhost%3A44300%2F' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I think you misunderstood the error message. It says that your AngularJS application was trying to make a request to https://login.microsoftonline.com/xxx-xx-41af-91ab-xxx/oauth2/authorize and failed, because it was a cross-origin request and the server didn't approve it by returning the Access-Control-Allow-Origin header in a response to preflight request (HTTP method OPTIONS).
So you cannot change it by adding CORS headers to your backend. The authorize is not designed to be called by XMLHttpRequest requests - you are supposed to make a full browser request to that URL. Later, the browser will be redirected to redirect_uri (the request parameter value) along with an auth code or an error.
In my application I have integrated Identity server 3 with openid-connect.
On our production server our website is behind a reverse proxy which is causing problems;
When the user logs in and is redirected back by identity server, our application wants to redirect the user to his original location (the page with the AuthorizeAttribute).
The problem here is that the user is redirected to the hidden url instead of the public url used by the reverse proxy.
How can I redirect the user to the public url?
After a long search this is the fix:
The OWIN middleware UseOpenIdConnectAuthentication has a property Notifications in the Options property.
This Notifications property has a func SecurityTokenValidated. In this function you can modify the Redirect Uri.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = "https://idp.io",
ClientId = "clientid",
RedirectUri = "https://mywebsite.io",
ResponseType = "code id_token token",
Scope = "openid profile",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = notification =>
{
notification.AuthenticationTicket.Properties.RedirectUri = RewriteToPublicOrigin(notification.AuthenticationTicket.Properties.RedirectUri);
return Task.CompletedTask;
}
}
});
This is the function which rewrites the url to the public origin:
private static string RewriteToPublicOrigin(string originalUrl)
{
var publicOrigin = ConfigurationManager.AppSettings["app:identityServer.PublicOrigin"];
if (!string.IsNullOrEmpty(publicOrigin))
{
var uriBuilder = new UriBuilder(originalUrl);
var publicOriginUri = new Uri(publicOrigin);
uriBuilder.Host = publicOriginUri.Host;
uriBuilder.Scheme = publicOriginUri.Scheme;
uriBuilder.Port = publicOriginUri.Port;
var newUrl = uriBuilder.Uri.AbsoluteUri;
return newUrl;
}
return originalUrl;
}
Now the OpenIdConnect redirects the user to the public url instead of the non-public webserver url.
I am trying to use ADFS Authentication with OAuth to communicate between my webapp and webapi. I am using ADFS4 and have configured application group with Server application and Webapi accordingly. I am trying to receive the userdetails, particularly the username from the webapi controller. Is it possible to pass the username details within the access token passed to webapi. Here is what I did from the Webapp side:
In the webapp controller after adfs authentication,
authContext = new AuthenticationContext(Startup.authority, false);
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
string accessToken = null;
bool isAuthenticated = User.Identity.IsAuthenticated; //return true
string username = User.Identity.Name; // returns username
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value; // returns username
HttpClient httpClient = new HttpClient();
try
{
result = authContext.AcquireTokenAsync(Startup.apiResourceId, credential).Result;
accessToken = result.AccessToken;
}
catch (AdalException ex)
{
}
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = httpClient.GetAsync(Startup.apiResourceId + "/api/ConfApi").Result;
From the Webapi end, in Startup.Auth.cs, I have added these code
public void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
TokenValidationParameters = new TokenValidationParameters() {
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
});
}
However, within the ConfApi controller, I cannot find any claims with user details.
What can I do to receive user details in the Webapi controller?
Thanks for any help.
Are you actually receiving the claims?
Did you configure claims rules for the web API on the ADFS side?
What did you use for Name - Given-Name, Display-Name etc?
Use something like Fiddler to monitor the traffic. After the OIDC authentication, you should see access tokens, id tokens etc.
Take the token and copy into jwt.io.
This will show you what you are actually receiving.
However, the OWIN classes translate the simple OAuth attributes e.g. "aud" into the claim type URI e.g. http://claims/this-claim so breakpoint and see what is in the claims collection and what type has been assigned to each.
The answer to this is the same answer to the question: MSIS9649: Received invalid OAuth request. The 'assertion' parameter value is not a valid access token
You have to use authorization code flow (instead of client credentials grant flow) to get the server app (web app in this case) to talk to the web API with the user's context. Authorization code flow will pass the claims in the JWT Token. Just make sure you pass thru any claims you need for the web API in the web API's RPT claim issuance transform rules.
Vittorio has a nice post on authorization code flow, although it talks about azure.
In order to use authorization code flow, you need to handle the AuthorizationCodeReceived Event via Notifications on the OpenIdConnectAuthenticationOptions from Startup.ConfigureAuth(IAppBuilder app)
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions {
...
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = async code => {
ClientCredential credential = new ClientCredential(Startup.clientId, Startup.appKey);
AuthenticationContext authContext = new AuthenticationContext(Startup.authority, false);
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
code.Code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
Startup.apiResourceId);
}
}
When you are ready to make the call you acquire your token silently.
var authContext = new AuthenticationContext(Startup.authority, false);
var credential = new ClientCredential(Startup.clientId, Startup.appKey);
var claim = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userId = new UserIdentifier(claim, UserIdentifierType.UniqueId);
result = await authContext.AcquireTokenSilentAsync(
Startup.apiResourceId,
credential,
userId);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
result.AccessToken);
We have implemented OAuth using IndentityServer and Owin self hosted web API's. Whenever a client tries to access our authenticated endpoints we use Microsoft.Owin.Security.OpenIdConnect middleware to intercept and redirect the call to the IndentityServer for authentication. In the case of an API call we do not want to return 302, but instead a 401 with a location header with the url of the IdentityServer. We can get the OWIN middleware to return a 401 by setting
AuthenticationMode = AuthenticationMode.Passive
but we're unable to add a location header. How do we accomplish this? We have tried setting the header (see code below), but it doesn't work. It seems the response is build internally by the middleware.
appBuilder.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = idSrvUri.ToString(),
AuthenticationType = WebServiceConstants.OAuthAuthType,
ClientId = "beocloud",
Scope = "openid profile roles",
ResponseType = "id_token token",
AuthenticationMode = AuthenticationMode.Passive,
SignInAsAuthenticationType = WebServiceConstants.OAuthAuthType,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
n.ProtocolMessage.RedirectUri = n.Request.Scheme + "://" + n.Request.Host + "/";
n.Response.Headers.Add("Location", new []{n.ProtocolMessage.CreateAuthenticationRequestUrl()});
}
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
}
This is the problem hosting a Web API along side your MVC code -- you're using the wrong security middleware for the API side of it. The OIDC middleware assumes the HTTP calls are from a browser window that it can redirect.
I'd suggest splitting your API into a separate host and pipeline and use a token-based security architecture and middleware. We have lots of samples of this pattern on the github repo.