CORS issue with UseOpenIdConnectAuthentication - c#

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.

Related

Unable to obtain username from WebAPI in ADFS4-OAuth2 OpenID

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);

HttpClient to call Azure AD-protected site

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.

Azure B2C Active Directory OpenIDConnect and Authorization Codes

I've set up my web app using OpenIDConnectAuthentication as follows. The OnAuthorizationCodeReceived notification uses Microsoft.IdentityModel.Clients.ActiveDirectory 3.13.8.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
MetadataAddress = Settings.AADB2CAuth.SignInPolicyMetaAddress, // https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration?p={policy} policy = B2C_1_SignIn
AuthenticationType = Settings.AADB2CAuth.SignInPolicyId, // B2C_1_SignIn
ClientId = Settings.AADB2CAuth.ClientId, // {guid}
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived
},
RedirectUri = Settings.AADB2CAuth.RedirectUri,
Scope = "openid",
ResponseType = "id_token",
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential clientCredential = new ClientCredential(Settings.AADB2CAuth.ClientId, Settings.AADB2CAuth.ClientSecret);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(Settings.ClaimTypes.ObjectIdentifier).Value;
string authority = Settings.AADB2CAuth.Authority; // https://login.microsoftonline.com/{tenant}
AuthenticationContext authContext = new AuthenticationContext(authority, new ADAL.ADALTokenCache(userObjectID));
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, redirectUri, clientCredential, Settings.AADGraphApi.GraphResourceId);
}
This works fine. However an authorization code is not returned with the id_token. If change this to code id_token or just code, the AuthorizationCodeReceived notification fires, but then I am met with the error
AADSTS70000: Authentication failed: Authorization Code is malformed or invalid
Basically what I'm trying to do is access the B2C AD as the current signed in user. Is this at all possible?
I updated my Authentication Options to
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = Settings.AADB2CAuth.SignInPolicyId,
Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}", Settings.AADB2CAuth.Tenant, Settings.AADB2CAuth.SignInPolicyId),
ClientId = Settings.AADB2CAuth.ClientId,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived
},
RedirectUri = Settings.AADB2CAuth.RedirectUri,
Scope = "openid",
ResponseType = "code id_token",
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential clientCredential = new ClientCredential(Settings.AADB2CAuth.ClientId, Settings.AADB2CAuth.ClientSecret);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(Settings.ClaimTypes.ObjectIdentifier).Value;
string authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}", Settings.AADB2CAuth.Tenant, Settings.AADB2CAuth.SignInPolicyId);
AuthenticationContext authContext = new AuthenticationContext(authority, new ADAL.ADALTokenCache(userObjectID));
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, redirectUri, clientCredential, Settings.AADGraphApi.GraphResourceId);
}
I am now met with an exception whose details are the HTML content of a 404 page. Looking at the requests I believe it is because AcquireTokenByAuthorizationCodeAsync is looking at https://login.microsoftonline.com/tfp/oauth2/token to send the authorization code to, which I don't think it should?
It may be worth noting that the Authorization Code header I get back is the following:
{
"kid": "cpimcore_09252015",
"ver": "1.0"
}
A quick google search for this yields one result and this references the following issue on the Android ADAL. I'm not sure if this relates to my issue however.
If you look at the beginning of this error:
AADSTSXXXXX
means that when you tried to exchange your auth code, you went to the AAD sts rather than the expected B2C sts:
AADB2CXXXXX
This means your auth code post request was interpreted incorrectly by our endpoint. This is usually caused when the policy (p=B2C_1_xxxx) param for B2C gets appended onto the post URL rather than inside the request.
Option 1:
Refactor your code and library usage to stick the policy param inside the auth code post request rather than the end of the token endpoint URL.
Option 2:
Use the alternate token endpoint and don't tack on any poliy param. Your new endpoint would look like this
https://login.microsoftonline.com/tfp/{tenant}/B2C_1_myB2CPolicy/oauth2/v2.0/token

How to send user token to browser when using OpenId connect with Azure AD?

I am connecting AzureAd with OpenId Connect, I am able to connect to https://login.microsoftonline.com/ with my tenantid, client id and perform the authentication using the below configuration
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
}
},
});
}
My problem is after getting the user token from authorization code i am unable to send it back to the browser. I need to send this token to the browser to perform api calls of the same site or another site registered to the same AD.
I tried to set cookies of both response and request but it didnt work, i cannot see the cookie values in Action.
Is there a way or events available to utilize the token generated from the above sample.
It would be help full if any one can point me to articles describing OpenId connect with AzureAd and Single Sign on.
Thanks in advance,
Mahesh Gupta
Have a look at OpenId protocol specifications. I have already used IdentityServer4 and if I use Implicit Flow, it sets access_token and id_token at response URL, like example.com/api/test#access_token=xxxxxxxx&id_token=xxxxxxxx. Try to append your API response URL in the same way (e.g. using Redirect) or add this data to your response as a new Header.

Adding location header on response from Microsoft.Owin.Security.OpenIdConnect middleware

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.

Categories

Resources