"Authorization has been denied for this request" when hosting Azure App Service Backend locally on IIS - access-token

In my Azure App Service .NET backend, I typically decorate my controllers with [authorize] so that they are protected against users who are not signed in. For uer authentication I set up an Azure Active Directory B2C (AADB2C) tenant, configured an application as well as some identity providers and everything works as expected as long as I am hosting my backend on Azure.
Now, I'd like to be able to run the backend locally so that I can easily debug the code and have it work against a local development database.
However, it does not work.
I went to https://[myappservice].scm.azurewebsites.net/ and retrieved the WEBSITE_AUTH_SIGNING_KEY from the environment tab.
I added the key to my backends' web.config as Signingkey under AppSettings
I changed ValidAudience and ValidIssuer to the respective values (https://[myappservice].azurewebsites.net)
So that I can bind the backend to an IP address or my hosts name rather than "localhost" (which would be the case with IIS Express), I configured the backend to run under IIS
I only allowed anonymous access on my IIS Website and let it run under the DefaultAppPool which uses the integrated pipeline on the 4.0 CLR
My client code does not use MobileServiceClient but MSAL (PublicClientApplication). So the documentation about how to setup a local dev environment found here does not apply.
Because I have a RequestProvider class that creates the necessary HttpClient and adds the access token to the header, this shouldn't be a problem, because for authentication, the PublicClientApplication object uses the AADB2C tenant and the respective server flow policies for signing in and -up and for calling the backends' rest controller I can switch between the Azure hosted and the locally hosted backend.
However, while authentication keeps working like a charm and I am able to receive a working access token, the backend that is hosted locally does not accept it while the same backend code hosted on azure does.
So I started searching the net and found out about the website jwt.io. I pasted my access token into their decoder and was astonished about the result.
Some article mentioned that the values ValidIssuer and ValidAudience configured in the backends' web.config need to exactly reflect the values iss and aud contained within the access token.
The values in my access token however don't match the scheme which was part of the web.config template though (https://[myappservice].azurewebsites.net).
The iss field in my decoded token looked like this:
https://[myappservice].b2clogin.com/12345678-1234-1234-1234-123456789012/v2.0/
The aud field in my decoded token looked like this:
12345678-1234-1234-1234-123456789012
The isser value can be found in my AADb2C tenants' directory within the signin/-up policies' settings under Properties in "Token compatibility settings" as Issuer (iss) claim. Theres a combobox from which the following value is selected:
https://<domain>/12345678-1234-1234-1234-123456789012/v2.0/
The aud fields' guid equals the Client ID configured in the App Service on Azure.
However, even if I take these two values and enter them into the web.config as described above, I still get an "Authorization has been denied for this request" error.
I really don't know how to get this working...

I tried to reproduce your issue and it works well if in my environment. I was referring to active-directory-b2c-dotnet-desktop as native client and active-directory-b2c-dotnet-webapp-and-webapi (host the api project on IIS, web app project is not used) as backend api.
I also encountered the same issue several times when testing, and resolved it by the following steps:
No special configuration is required on IIS side. You don't need to specify "Signingkey" in web.congig because when validating the token, signing key will be fetched automatically from metedata endpoint (jwks_uri field):
https://TenantName.b2clogin.com/TenantName.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=SignInPolicyName
In Azure Portal, make sure you have granted native client the access to api app.
For other basic configuration, refer to the instruction of samples above.
I also suggest you decode the access token and confirm the primary claims are correct:
"iss" should be same as the AAD instance in API app
"aud" should be client id
"sub" or "oid" should be the object id of the logged in user.
"tfp" should be the sign in policy name
"scp" should be published scope of the API app
For other claims, please refer to Azure AD B2C: Token reference
Note, the official samples above are still using login.microsoftonline.com as authority endpoint, intsead of b2clogin.com. To use b2clogin, just update the endpoint accordingly.
The below is core code snippet and settings in my working sample:
Native client
public partial class App : Application
{
private static string TenantName = "<TenantName>";
private static string Tenant = "<TenantName>.onmicrosoft.com";
private static string ClientId = "<ClientID>";
public static string PolicySignUpSignIn = "B2C_1_signupsignin1";
public static string PolicyEditProfile = "b2c_1_edit";
public static string PolicyResetPassword = "b2c_1_reset";
public static string[] ApiScopes = { "https://<TenantName>.onmicrosoft.com/demoapi/demo.read" };
public static string ApiEndpoint = "http://<IISServerAddress>/taskservice/api/tasks";
private static string BaseAuthority = "https://login.microsoftonline.com/tfp/{tenant}/{policy}/oauth2/v2.0/authorize";
private static string BaseAuthorityForb2clogin = "https://{TenantName}.b2clogin.com/tfp/{tenant}/{policy}";
public static string Authority = BaseAuthorityForb2clogin.Replace("{tenant}", Tenant).Replace("{policy}", PolicySignUpSignIn).Replace("{TenantName}", TenantName);
public static PublicClientApplication PublicClientApp { get; } = new PublicClientApplication(ClientId, Authority, TokenCacheHelper.GetUserCache());
}
API app config:
<add key="ida:AadInstance" value="https://tomtestb2c.b2clogin.com/{0}/v2.0/.well-known/openid-configuration?p={1}" />
<add key="ida:Tenant" value="TenantName.onmicrosoft.com" />
<add key="ida:ClientId" value="ClientId-ff8e-4ac2-a093-6c6e0f8e116c" />
<add key="ida:SignUpSignInPolicyId" value="B2C_1_signupsignin1" />
<!-- The following settings is used for requesting access tokens -->
<add key="api:ReadScope" value="read" />
<add key="api:WriteScope" value="write" />
Auth code:
public partial class Startup
{
// These values are pulled from web.config
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string SignUpSignInPolicy = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicy;
/*
* Configure the authorization OWIN middleware
*/
public void ConfigureAuth(IAppBuilder app)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
ValidAudience = ClientId,
AuthenticationType = Startup.DefaultPolicy
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(AadInstance, Tenant, DefaultPolicy)))
});
}
}

Related

How to add the Keycloak role offline_access to a newly created user via keycloak_admin_client?

I am using the keycloak-admin-client 20.0.1 in Spring Boot 2.7.4 application and want to add the additional role offline_access automatically to the role mapping of certain users. Something like this...
private final static String USER_ACCESS_ROLE = "machine";
private final static String OFFLINE_ACCESS_ROLE = "offline_access";
var userAccessRole = realmResource.roles().get(USER_ACCESS_ROLE).toRepresentation();
var accessOfflineRole = realmResource.roles().get(OFFLINE_ACCESS_ROLE).toRepresentation();
userResource.roles().realmLevel().add(List.of(userAccessRole, accessOfflineRole));
I can add other self defined roles, but not the predefined offline_access role. I don't get any exception or error message.
My Keycloak admin client looks like this:
private Keycloak getKeycloakInstance() {
return KeycloakBuilder.builder()
.serverUrl(env.getRequiredProperty(KEY_KEYCLOAK_ADMIN_URL))
.realm(env.getRequiredProperty(KEY_KEYCLOAK_ADMIN_REALM))
.username(env.getRequiredProperty(KEY_KEYCLOAK_ADMIN_USERNAME))
.password(env.getRequiredProperty(KEY_KEYCLOAK_ADMIN_PASSWORD))
.clientId(KEYCLOAK_ADMIN_CLIENTID)
//.scope("")
.grantType(OAuth2Constants.PASSWORD)
.resteasyClient(ClientBuilder.newBuilder().build())
.build();
}
I did not set the scope here, but if it is set to "offline_access" it doesn't help.
UPDATE
I've updated Keycloak from version 15.0.1 to 19.0.3 and now it's working without any other changes!

Bearer token: The signature is invalid - Default ASP.NET Core 2.1 Web Api template published to Azure

I created a project in VS Community 2019 (latest update) with a template for WebApi .NET Core 2.1 and published it on Azure.
I only added a client secret in the app registration in the portal to use for the call using the authorization code flow.
I was trying to make a GET call using Postman with OAuth 2.0 authorization at the url below:
https://webapi3app.azurewebsites.net/api/values
But I get an unauthorized response with the error header below:
WWW-Authenticate:"Bearer error="invalid_token", error_description="The signature is invalid""
I tried decoding the client secret to BASE64 string but in the repsonse it says that it's an invalid client secret.
I tried changing authorization data to:
- Request URL.
- Request Headers.
I tried several grant types:
- Authorization code.
- Implicit.
- Password Credentials (after changing app to public client).
I tried several scopes:
- Default Microsoft scopes url (https://graph.microsoft.com/.default).
- user.read openid profile offline_access.
- https://aldolekalive.onmicrosoft.com/WebApi3/user_impersonation.
- profile openid email https://graph.microsoft.com/Directory.Read.All https://graph.microsoft.com/User.Read
I tried setting client authentication to:
- Send as basic auth header.
- Send client credentials in body.
I tried changing the Authorize attribute to authorize based on only AzureADBearer or only AzureADJwtBearer (because apparently by default they are both enabled with the current configuration) but no luck.
etc.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
I expect to get a response with the body:
"value1, value2"
Per my understanding, your webapi is protected by Azure AD and now you want to call the api. To call the api you need to provide an access token.
To do this, you need to register two applications in Azure AD. One is for client App, the other one is for webapi. You can refer to my answer here.
And here is the complete sample. If you don't have an client application now, you can just register an client app in Azure portal, then use this client app to get an access token for your webapi.
I tried several scopes:
If you are using v2.0 endpoint, the scope should be api://{server_client_id}/.default.

Role based authorization in asp.net web api using ad access token

I need to implement role based authorization for asp .net web api
from native client application the access token will be sent in the header ,
so when i give [Authorize] attribute to my web api it is working fine,but when i give [Authorize(Role="Admin")] it is giving unauthorized error,
then whenever check for role in the claims it is always null.
My startup.cs is
public void Configuration(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
});
}
and i have applied authorize attribute for a method
[Authorize]
[HttpGet]
public async Task<UserDetails> TestAuthorization()
{
string upn = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var role = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Role);
return null;
}
here the role value is always null
Please help me on this

Twitter login with Spring Social not working

I have a working Spring Boot app using Oauth2 authentication (password grant type). I now need to support Facebook and Twitter login.
I am using a custom TokenGranter to allow a client to send me a Facebook access token or Twitter consumer id and secret so they can be logged into my server application and receive an OAuth2 access_token I generate. I have this working for Facebook using FacebookConnectionFactory:
Connection<Facebook> connection = facebookConnectionFactory.createConnection(new AccessGrant(providerToken));
With the connection, I get the user id of Facebook:
String providerUserId = connection.getKey().getProviderUserId();
With this id, I search if there is such a user in my UserRepository and if so, I log in the user:
CustomUserDetails userDetails = new CustomUserDetails(user);
userAuth = new SocialAuthenticationToken(connection, userDetails,
null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(userAuth);
This all works fine. Doing the same with Twitter:
Connection<Twitter> connection = twitterConnectionFactory.createConnection(new OAuthToken(providerToken, tokenSecret));
Gives me this exception:
Unable to connect with Twitter: Authorization is required for the operation,
but the API binding was created without authorization.
What I find strange is that using TwitterTemplate with the same app id and secret and consumer and consumer secret does work.
TwitterTemplate twitterTemplate = new TwitterTemplate(...);
UserOperations userOperations = twitterTemplate.userOperations();
AccountSettings accountSettings = userOperations.getAccountSettings();
I need the Connection<Twitter> object for the SocialAuthenticationToken. What am I doing wrong?
Stupid mistake, I had:
#Value("spring.social.twitter.app-id")
private String twitterAppId;
#Value("spring.social.twitter.app-secret")
private String twitterAppSecret;
in stead of:
#Value("${spring.social.twitter.app-id}")
private String twitterAppId;
#Value("${spring.social.twitter.app-secret}")
private String twitterAppSecret;
So I put the literal text spring.social.twitter.app-id instead of the value of the property.

WebApi with OWIN SelfHost and Windows Authentication

I have a console application SERVER that hosts WebApi controllers using OWIN self-hosting, and runs under a custom account named "ServiceTest1".
In the same machine I have another console application CLIENT that runs under the account "ServiceTest2", and I want to capture in SERVER that "ServiceTest2" invoked a controller action. However:
WindowsIdentity.GetCurrent() is always "ServiceTest1".
Thread.CurrentPrincipal is an unauthenticated GenericIdentity.
RequestContext.Principal is null.
User is null.
What do I need to make this WebApi OWIN self-hosted to grab the Windows identity of the caller?
Your question is a little unclear on exactly how you've implemented the Windows authentication.
Enable Windows authentication:
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
// ...
}
}
Get the user in an OWIN middleware:
public async Task Invoke(IDictionary<string, object> env)
{
OwinContext context = new OwinContext(env);
WindowsPrincipal user = context.Request.User as WindowsPrincipal;
//...
}
Get the user in a Web API Controller:
// In a web api controller function
WindowsPrincipal user = RequestContext.Principal as WindowsPrincipal;

Resources