As a follow-up on my question about how to setup a ROPC Flow. I want to access my API through the ROPC flow (currently using default user flows) and also through my web app which uses a custom policy on sign-in. This results in two different access tokens. On the left is access token received using the AcquireTokenSilent call and on the right is the access token received through postman with ROPC.
The custom policy token (on the left) gives an "Authorization has been denied for this request." error, while the token on the right is fine. I am assuming that the custom policy token does not work because it does not contain the tfp claim (and if it did, it would be a different one).
How can I set it up so that I can still use the ROPC flow while also using the custom policy? I would like to keep the current userjourney in the custom policy the same. Although if it is possible to somehow add ROPC as an option to it, then it would be fine.
Based on the description above, you are using two policy types - a user flow and a custom policy. And, you are attempting to get SSO between the two.
This is not a supported scenario. This is because the token uses different keys that signs the token.
If custom policies are required for your scenario, I suggest converting the user flow ROPC to a custom policy using this document https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy?tabs=app-reg-ga&pivots=b2c-custom-policy
So I finally found a way to do this in .NET Framework, if you want a solution for .NET Core you would sadly have to look somewhere else.
In your startup add the following.
/*
* Configure the authorization OWIN middleware
*/
private void ConfigureAuthenticationAzure(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(CreateOptions(ClientId, SignUpSignInPolicy, azureDiscoveryEndpoint));
app.UseOAuthBearerAuthentication(CreateOptions(ClientId, ApiPolicy, azureDiscoveryEndpointAPI));
}
private OAuthBearerAuthenticationOptions CreateOptions(string audience, string policy, string discoveryEndpoint)
{
var metadataEndpoint = String.Format(discoveryEndpoint, Tenant, policy);
// This is the default check, in OnValidateIdentity, we check for more.
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = ClientId,
ValidateAudience = true,
AuthenticationType = policy,
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier",
ValidateIssuer = true,
};
return new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint)),
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = async context =>
{
try
{
var authorizationHeader = context.Request.Headers.Get("Authorization");
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
var ticket = context.Ticket;
//var identity = ticket.Identity;
var jwtSecurityToken = new JwtSecurityToken(userJwtToken);
var expiration = jwtSecurityToken.ValidTo.ToLocalTime();
if (expiration < DateTime.Now)
{
log.Warn("The JWT token has expired.");
context.Rejected();
return;
}
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(discoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdconfig = configManager.GetConfigurationAsync().Result;
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openIdconfig.SigningKeys,
ValidateIssuer = true,
ValidIssuer = $"{AzureIssuer.ToLower()}/v2.0/",
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
//ClockSkew = TimeSpan.Zero
};
var handler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = handler.ValidateToken(userJwtToken, validationParameters, out securityToken);
var policyName = principal.FindFirst("tfp")?.Value;
// Add the name claim type for this authentication type
if (policyName.ToLower() == DefaultPolicy.ToLower()) // Sign In Only policy...
{
// Run specific code here for the policy that just sent a token back to the application...
context.Validated(ticket);
return;
}
else if (policyName.ToLower() == SignUpSignInPolicy.ToLower())
{
context.Validated(ticket);
return;
}
context.Rejected();
return;
}
catch(Exception ex)
{
context.Rejected();
return;
}
}
}
};
}
Related
We want to switch our web apps to using Azure AD for authentication.
I'm using the example at: https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
It wouldn't work initially but after some modifications I got authentication to work. But when it redirects back to my app I get this error: IDX10501: Signature validation failed. Unable to match key: kid
I'm using https://login.microsoftonline.com/{0}/v2.0 for the authority where {0} is my tenant id.
This is the authentication code
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Custom middleware initialization. This is activated when the code obtained from a code_grant is present in the querystring (&code=<code>).
app.UseOAuth2CodeRedeemer(
new OAuth2CodeRedeemerOptions
{
ClientId = AuthenticationConfig.ClientId,
ClientSecret = AuthenticationConfig.ClientSecret,
RedirectUri = AuthenticationConfig.RedirectUri
});
IdentityModelEventSource.ShowPII = true;
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// This is needed for PKCE and resposne type must be set to 'code'
UsePkce = true,
ResponseType = OpenIdConnectResponseType.Code,
// The `Authority` represents the v2.0 endpoint - https://login.microsoftonline.com/{0}/v2.0
Authority = AuthenticationConfig.Authority,
ClientId = AuthenticationConfig.ClientId,
ClientSecret = AuthenticationConfig.ClientSecret,
RedirectUri = AuthenticationConfig.RedirectUri,
PostLogoutRedirectUri = AuthenticationConfig.RedirectUri,
Scope = AuthenticationConfig.BasicSignInScopes + " Mail.Read User.Read", // a basic set of permissions for user sign in & profile access "openid profile offline_access"
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
},
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/samesite/owin-samesite
CookieManager = new SameSiteCookieManager(
new SystemWebCookieManager())
});
}
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> arg)
{
arg.ProtocolMessage.SetParameter("myNewParameter", "its Value");
return Task.CompletedTask;
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
context.TokenEndpointRequest.Parameters.TryGetValue("code_verifier", out var codeVerifier);
// Upon successful sign in, get the access token & cache it using MSAL
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "Mail.Read User.Read" }, context.Code)
.WithSpaAuthorizationCode() //Request an authcode for the front end
.WithPkceCodeVerifier(codeVerifier) // Code verifier for PKCE
.ExecuteAsync();
HttpContext.Current.Session.Add("Spa_Auth_Code", result.SpaAuthCode);
// This continues the authentication flow using the access token and id token retrieved by the clientApp object after
// redeeming an access token using the access code.
//
// This is needed to ensure the middleware does not try and redeem the received access code a second time.
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
//notification.HandleResponse();
//notification.Response.Redirect("/Error?message=" + notification.Exception.Message);
return Task.FromResult(0);
}
How do I get it to validate the signature?
Thanks,
Skye
Please check if below points help:
On successful authentication, Azure AD issues a signed JWT token (id token or access token). The resource application needs to know the public key of the certificate used sign the token in order to validate the token signature . An OWIN asp.net application can throw the following error IDX10501: Signature validation failed. Unable to match ‘kid’ or IDX10501: Signature validation failed. Unable to match key when it’s not able to find the kid to validate the token signature:
Please check and add valid audience and issuer. And check if the kid
its validating is for symmetric key.
Check if you are passing the correct metadata endpoint.
ex:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
MetadataAddress = "https://login.microsoftonline.com//.well-known/openid-configuration?appid="
PostLogoutRedirectUri = redirectUri,
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{validissuers.Last()}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openidconfig = configManager.GetConfigurationAsync().Result;
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidAudience = config.Resource,
ValidateIssuer = true,
ValidIssuers = new[] { config.GetAuthority() },
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openidconfig.SigningKeys,
//or IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SuperSecretPassword2"))
}
}
Azure AD by default uses a certificate to sign an OAuth2 JWT token
using an asymmetric algorithm (RS256). Alternatively a JWT token can
be signed with a “shared” secret using a symmetric algorithm (HS256).
References:
get a symmetric key signing token (aaddevsup.xyz)
c# - IDX10501- Stack Overflow
Im having an issue with a web app in azure. I use active directory to control access which works well, but one of my users gets this error message when they login in
idx10214 audience validation failed did not match validationparameters.validaudience or validationparameters null
does anyone know what this means ? Is there a workaround ?
heres how I setup openid to authorize users, how would i include the audience settings here ? What should the audience value be ?
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = AuthorityCHP,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = loURL,
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;
Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential credential = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(AuthorityCHP, new ADALTokenCache(signedInUserID));
var newuri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, newuri, credential, graphResourceId);
return Task.FromResult(0);
}
}
});
Ive added some additional code in the hope it might work, but after i deployed it, i got the user affecetd to try logging in again and its still the same. Im not consuming a webapi its just a straightforwrsd webapp with a standard login page, Im really stumped, why is it only one particular user thats affecetd by this ? Can anynoe help ?
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId
},
Tenant = tenantId,
AuthenticationType = "OAuth2Bearer"
});
That's mean you were calling the API using the incorrect token. When we call a web API which protected by Azure AD, it will verify the signature of the token and claims in the token.
The audience is used to which resource the token is able to access. We should acquire the token based on the resource. For example, if we protect the web API with code below, we should use the audience config in the below to acquire the token.
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
});
//app.UsePasswordAuthentication();
}
update
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
// In a real application you would use IssuerValidator for additional checks, like making sure the user's organization has signed up for your app.
// IssuerValidator = (issuer, token, tvp) =>
// {
// //if(MyCustomTenantValidation(issuer))
// return issuer;
// //else
// // throw new SecurityTokenInvalidIssuerException("Invalid issuer");
// },
},
I followed official steps as below to try the scenario "web app calling a Web API in Azure Ad B2C", the only difference is I am using Asp.Net core. I am using AuthorizationCode to get the access token, but it always returns with id token and NULL access token.
Create an Azure AD B2C tenant.
Register a web api.
Register a web app.
Set up policies.
Grant the web app permissions to use the web api.
My code:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme,
AutomaticChallenge = true,
ClientId = aadB2cSettings.ClientId,
MetadataAddress = $"{aadB2cSettings.Instance}{aadB2cSettings.Tenant}/v2.0/.well-known/openid-configuration?p={aadB2cSettings.B2cSignUpOrSignInPolicy}",
PostLogoutRedirectUri = aadB2cSettings.RedirectUrl,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async context =>
{
var authCode = context.TokenEndpointRequest.Code;
var b2cAuthority = $"{aadB2cSettings.Instance}tfp/{aadB2cSettings.Tenant}/{aadB2cSettings.B2cSignUpOrSignInPolicy}/v2.0/.well-known/openid-configuration";
var cca = new ConfidentialClientApplication(
aadB2cSettings.ClientId,
b2cAuthority,
aadB2cSettings.RedirectUrl,
new ClientCredential(aadB2cSettings.ClientSecret),
new TokenCache(),
null);
try
{
var authResult = await cca.AcquireTokenByAuthorizationCodeAsync(authCode, new[] { "https://hulab2c.onmicrosoft.com/b2cdemo/all" });
context.HandleCodeRedemption(authResult.AccessToken, authResult.IdToken);
}
catch (Exception ex)
{
throw ex;
}
}
},
Used fiddler to capture the request, it is:
POST
https://login.microsoftonline.com/hulab2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_signuporsignin
HTTP/1.1
Request Body:
client_id=1ff91f47-08ee-4973-83f4-379ad7e0679c&client_info=1&client_secret=......&scope=https%3A%2F%2Fhulab2c.onmicrosoft.com%2Fb2cdemo%2Fall+offline_access+openid+profile&grant_type=authorization_code&code=......&redirect_uri=https%3A%2F%2Flocalhost%3A44383%2F
Return:
{"id_token":"......","token_type":"Bearer","not_before":1494494423,"client_info":"......","scope":""}
So only id token, no access token. But we should get access token here, right?
Finally found out my failure reason: the request to get AuthorizationCode doesn't contain the target scope. Reflect in code, for OpenIdConnectOption in aspnetcore, the Scope parameter is readonly and its default value is "opened profile".
Scope is readonly in OpenIdConnectOption
So the default authorization code request sent is:
GET
https://login.microsoftonline.com/hulab2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_signuporsignin&client_id=7f865ca0-271e-4f27-be21-6f0072fe3ad7&redirect_uri=https%3A%2F%2Flocalhost%3A44355%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile&response_mode=form_post&nonce=......
HTTP/1.1
Thus, using this authorization code in response to get token, even we set right scope in the token request, we still can't get the access code but only id token, because the provide authorization code is only for "openid profile".
To fix this, we need to add target web api scope into the authorization code as well. Here is the how-to-fix code:
Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Scope += $" offline_access {myapiscope}";
return Task.FromResult(0);
},
......
}
In AspNet, we don't need to do this because its scope is not readonly as aspnetcore and can be set directly:
new OpenIdConnectAuthenticationOptions
{
......
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}"
}
https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/4 Microsoft have reproduced the issue and working on fix
I have setup 3rd party login providers (google and microsoft) on Azure for my web app. I am adding a custom login also (in case the user doesn't have one of those accounts). I have the token being generated as follows:
private JwtSecurityToken GenerateToken(User user)
{
var audience = ConfigurationManager.AppSettings["ValidAudience"];
var issuer = ConfigurationManager.AppSettings["ValidIssuer"];
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, user.AzureUserID) },
GetSigningKey(),
audience,
issuer,
TimeSpan.FromHours(1));
return token;
}
Where GetSigningKey() returns a string of the Azure environment variable WEBSITE_AUTH_SIGNING_KEY. The default authentication with my API controllers is working great, but when I need to refresh my token due to it expiring I am having trouble validating its signature. There are a lot of examples out there on how to validate JwtSecurityTokens, but I haven't been able to find any when you signed it using the WEBSITE_AUTH_SIGNING_KEY. Here is what I have tried to get working, without success:
var validationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ValidAudience"],
ValidIssuer = ConfigurationManager.AppSettings["ValidIssuer"],
IssuerSigningToken = <I think I need this, but not sure how to populate it...>,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true
};
SecurityToken validatedToken;
tokenHandler.ValidateToken(expiredAccessToken, validationParameters, out validatedToken);
Does anyone have any ideas on how to validate the token?
App Settings (including the signing key you mentioned) appear as environment variables, so you can either use the ConfigurationManager to read it or you can use the normal environment variable reading for ASP.NET
Try:
ConfigurationManager.AppSettings["WEBSITE_AUTH_SIGNING_KEY"]
Digging through the unit tests on GitHub for the azure-mobile-apps-net-server project, I found an AppServiceTokenHandler that does the trick:
// Validate that the token was generated by our server.
var validationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ValidAudience"],
ValidIssuer = ConfigurationManager.AppSettings["ValidIssuer"],
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = false // The token is valid for this test case whether or not it is expired.
};
ClaimsPrincipal claimsPrincipal;
if (AppServiceTokenHandler.TryValidateToken(validationParameters, expiredAccessToken, GetSigningKey(), out claimsPrincipal))
{
// Token is validated
}
Our Mvc/WebAPI solution currently has four trusted identity providers which we have registered in ADFS3. Each of these identity providers can be used by our users by direct links, effectively working around any home-realm-cookies that ADFS may have created (eg: www.ourportal.com/accounts/facebook or www.ourportal.com/accounts/twitter). Currently we are migrating from WIF to OWIN but will keep using WS-Federation protocol for the time being by implementing wsfederation and cookie authentication middleware. When using WIF, we did the following in order to go directly to a known identity provider:
var signInRequest = new SignInRequestMessage(stsUrl, realm) { HomeRealm = homeRealm };
return new RedirectResult(signInRequest.WriteQueryString());
This seems to have two concerning behaviors, it does not pass the WsFedOwinState parameter, and on the return back to the Relying Party, the Home.cshtml is built (with a windows principal) before the the Owin authentication middleware is fired. The Home.cshtml being fired before the Owin middleware is the most concering as this view relies on Claims that would is provided in the transformation done by the authentication pipeline, which is fired afterwards and thus our view does not work. It works in the correct order when going to the portal in the normal way (eg www.ourportal.com)
I understand that in order to provide the Whr parameter, you do the following when configuring the ws-federation middleware:
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "SomeUrnOfAnIdentityProvider";
return Task.FromResult(0);
}
but this sets a single identity provider for the whole solution and does not allow our users to go directly to one of a list of identity providers.
The non-working method which builds the sign-in-request is currently:
private RedirectResult FederatedSignInWithHomeRealm(string homeRealm)
{
var stsUrl = new Uri(ConfigurationManager.AppSettings["ida:Issuer"]);
string realm = ConfigurationManager.AppSettings["ida:Audience"];
var signInRequest = new SignInRequestMessage(stsUrl, realm)
{
HomeRealm = homeRealm
};
HttpContext.Request.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return new RedirectResult(signInRequest.WriteQueryString());
}
The ws-federation and cookie middleware are configured as the first middleware in OWIN startup and the default authentication is set to
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I think I found a solution. The new method for skipping the home realm screen would be like this :
private void FederatedSignInWithHomeRealm(string homeRealm)
{
HttpContext.Request
.GetOwinContext()
.Authentication
.SignOut(CookieAuthenticationDefaults.AuthenticationType);
var authenticationProperties = new AuthenticationProperties { RedirectUri = "/" };
authenticationProperties.Dictionary.Add("DirectlyToIdentityProvider", homeRealm);
HttpContext.GetOwinContext().Authentication.Challenge(authenticationProperties);
}
And the OWIN WS-Federation middleware would be configured like this :
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = notification =>
{
string homeRealmId = null;
var authenticationResponseChallenge = notification.OwinContext
.Authentication
.AuthenticationResponseChallenge;
var setIdentityProvider = authenticationResponseChallenge != null
&& authenticationResponseChallenge.Properties
.Dictionary
.TryGetValue("DirectlyToIdentityProvider", out homeRealmId);
if (setIdentityProvider)
{
notification.ProtocolMessage.Whr = homeRealmId;
}
return Task.FromResult(0);
}
},
MetadataAddress = wsFedMetadata,
Wtrealm = realm,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = realm
}
});