Enabling Azure AD Auth for Swagger UI hosted in Service Fabric - azure

We enabled Swagger for Web API application which is hosted on Azure Service Fabric. We want to enable security on Swagger UI. So I followed below url which enables security –
https://blogs.msdn.microsoft.com/pratushb/2016/04/28/enable-swagger-to-authenticate-against-azure-ad/
https://github.com/domaindrivendev/Swashbuckle/issues/671 (response from Oleksandr-Tokmakov)
I could see the “Available authorizations” popup and I could see AAD authentication done successfully on another tab on click of Authorize button. But once authentication completed, I see the token not returns back to swagger ui and the authentication tab not closes.
Below is the code I used. (I created two AAD, one for Web Services hosted on Service Fabric and another for Swagger UI)
config.EnableSwagger(
c =>
{
c.SingleApiVersion("v1", "Title of Service");
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl("https://login.microsoftonline.com/tenentId-guid/oauth2/authorize")
.Scopes(scopes =>
{
scopes.Add("user_impersonation", "Access Services Local Swagger Secure");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
}
).EnableSwaggerUi(c =>
{
c.EnableOAuth2Support(
clientId: "Swagger AAD application Client Id",
clientSecret: "Swagger AAD application Key",
realm: "https://localhost:444/swagger/ui/o2c-html",
appName: "https://serviceslocal/swagger/", // Free text, no reference to AAD
scopeSeperator: "",
additionalQueryStringParams: new Dictionary<string, string>() { { "resource", "Web API AAD application Client Id" } }
);
}
);
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
// Correspond each "Authorize" role to an oauth2 scope
var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<AuthorizeAttribute>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
if (scopes.Any())
{
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", scopes }
};
operation.security.Add(oAuthRequirements);
}
}
}

I'm using Auth0, but here's what I got for OAuth2 that works for me.
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = Path.Combine(auth0Settings["Authority"].Value, "authorize")
});
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "docs";
c.InjectOnCompleteJavaScript("/swagger-ui.js");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Easy Streak API");
c.ConfigureOAuth2(auth0Settings["ClientId"].Value, auth0Settings["ClientSecret"].Value, auth0Settings["Authority"].Value, "EasyStreak API");
});

Related

API returning 401 on Azure but not when run locally

The application in question is a web site which has an API tacked on the side that reuses the many of the data access methods developed for the website. So there maybe some interference between the web site authentication/authorization and the API's. But if that was the case I don't understand why it works locally.
When I run locally, I can test the API using Swagger or Postman to login, get the Bearer token and use it to call the API methods. On Azure, although the login succeeds the next call to the API returns a 401:
www-authenticate: Bearer error="invalid_token",error_description="The signature is invalid"
The most obvious difference is the appsettings.json which I've copied to the Application Configuration blade on Azure:
The original appsettings looked like:
"Rt5": {
"BaseAddress": "https://localhost:44357"
},
"Azure": {
"BuildNumber": ""
},
"AuthentificationJWT": {
"Audience": "ddt-ssp",
"Issuer": "ddt-rt5",
"SecurityKey": "not so secret"
},
Usage - ConfigureServices():
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/Home/Unauthorized";
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthentificationJWT:SecurityKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AuthentificationJWT:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AuthentificationJWT:Audience"],
ValidateLifetime = true,
ValidAlgorithms = new HashSet<string> { SecurityAlgorithms.HmacSha256 },
ValidTypes = new HashSet<string> { "JWT" }
};
});
services.AddAuthorization(o =>
{
o.AddPolicy(AllowViewExceptions.Policy, p
=> p.Requirements.Add(new AllowViewExceptions.Requirement()));
o.AddPolicy(AllowSubmitException.Policy, p
=> p.Requirements.Add(new AllowSubmitException.Requirement()));
});
Usage - Configure():
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
if (UseSwagger)
{
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); });
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Confession: this isn't my code and I'm sure why we have the Cookie stuff mentioned though is does seem to be offer an alternative authorization mechanism. See here:
public string GetAuthorization()
{
//TODO: use ExtractSecurityToken();
var claimToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName);
if (claimToken != null)
{
return $"Bearer {claimToken.Value}";
}
else if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var headerToken))
{
return headerToken;
}
return null;
}
private JwtSecurityToken ExtractSecurityToken()
{
var accessToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName)?.Value;
if (accessToken == null &&
_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var value))
{
accessToken = value.ToString().Replace("Bearer ", ""); // TODO: Find a better way then extracting Bearer e.g Get token without scheme
}
if (accessToken == null)
{
return null;
}
return (JwtSecurityToken)new JwtSecurityTokenHandler().ReadToken(accessToken);
}
The problem appears to be with Azure Websites' "Authentication / Authorization" option, which when enabled prevents the Web Api from accepting requests with the Authentication header. Disabling the option and using the Owin library with Azure AD provides the desired solution.
Every app service that is associated with Azure-AD has a corresponding Azure-AD application declaration of type Web app/API. This resource id is the "App ID URI" in the app service's Azure-AD application declaration.
To debug Authentication issues, please refer this msdn link.
It turned out the problem was caused by the bizarre architecture surround the application. The Access Token value (including the SHA256 checksum calculated using the security key) was being generated in another application and passed to this one to use for its communications.
When run localling both applications were using a security key stored in their appsettings.json file and were the same. The pipeline which deployed these applications to Azure was substituting security keys for random values and so they differed.
I've left the question here undeleted because Suryasri's link may help others later.

Cannot see role claims in access token when contacting my ASP.NET Core 3.1 API from browser app

The browser app
I have a browser app (CRA, TypeScript) which is issuing, after successfully authenticating to Azure AD, a request to my API:
public async acquireAccessToken(): Promise<string | undefined> {
let res: AuthResponse | undefined = undefined;
const params: AuthenticationParameters = {
scopes: ["Users.Read"],
};
try {
res = await this.msal.acquireTokenSilent(params);
} catch (error) {
res = await this.msal.acquireTokenPopup(params);
}
return !res || !res.accessToken ? undefined : res.accessToken;
}
The one before is a utility method to get the access token to contact the API, the actual call is here:
const token = await acquireAccessToken();
const res = await fetch("/controller/test", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
});
console.log(res.text());
Where msal is the UserAgentApplication I am using as client to handle authentication and authorization in my browser app.
I have everything correctly set up in Azure where a registration app is used to represent the browser app, and another registration app is used to describe the API I need to contact.
The API
The API server is an ASP.NET Core 3.1 C# application whose Startup.cs is:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
}
}
I have removed all the extra code and left the parts that concern auth.
The controller I am contacting is:
[ApiController]
[Route("controller")]
public class MyController : ControllerBase
{
[HttpGet("test/")]
[Authorize(Roles = "Admin")]
public async Task<string> Test()
{
return "Ok";
}
[HttpGet("test2/")]
[Authorize]
public async Task<string> Test2()
{
return "Ok";
}
[HttpGet("test3/")]
public async Task<string> Test3()
{
return "Ok";
}
}
Azure
The setup in Azure is simple: apart from the two app registrations for the browser app and the API, I have set in the browser app registration some custom roles and assigned them to some users.
I authenticate in the browser app using a user who has the Admin app role assigned to it.
The problem
When my client app tries to fetch data using these endpoints:
/controller/test3
/controller/test2
Everything is fine as one is unprotected and the other one uses a simple [Authorize].
However when trying to fetch from /controller/test, I get 403 (Forbidden).
Why can't I make the roles work?
More info
While debugging when fetching test2, I can see, in the controller, that this.User is present and there are several claims. Among those claims, I cannot see anything relating to the role. The access token I get has the following form:
{
"aud": "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"iat": 1607034050,
"nbf": 1607034050,
"exp": 1607037950,
"acr": "1",
"aio": "AWQAm/8RAAAAH1j5tZzINJFi5fsMsgf99gcrnqQA+dOhWBpFmsgy3jsr0pFJ0AxvenqthiNLmRqKzqx6l+9SuLlRniAVCTOoqEE7MonnOetO3h7g1/Bm520rS0qiX/gpCCWYm/UwDlJ+",
"amr": [
"pwd"
],
"appid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"appidacr": "0",
"email": "xxx#xxx.xxx",
"idp": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"ipaddr": "xxx.xxx.xxx.xxx",
"name": "XXX",
"oid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"rh": "0.AAAASupzDdEU8EyBI3R6nFeJQHVORvhJZ2hDjJoEO5yPUcZ0AEU.",
"scp": "Users.Read",
"sub": "frR45l2dTAIyXZ-3Yn2mGNbBcBX9CrGisgJ4L8zOCd4",
"tid": "0d73ea4a-14d1-4cf0-8123-747a9c578940",
"unique_name": "xxx#xxx.xxx",
"uti": "39dk-rAAP0KiJN5dwhs4AA",
"ver": "1.0"
}
As you can see, no claim relating to roles.
But note that I can successfully get the role in the user token I get when authenticating. I need that claim to flow in the access token too when I use it to contact the API. How?
I found your problem:
You cannot set a custom role in the manifest of the browser application. You need to set a custom role in the manifest of the api application, and then assign the role to the user.
Then you need to use the auth code flow to get the access token, and the roles claims will be included in it.
In fact, the access token is created based on the intended recipient of your token (ie your api). If you want to access the api, you must have permissions or roles. In your question, this role is you Custom, when you grant the user a custom role of the api application, then the user has the role that can access the api application. (Note that this is not to assign the custom role of the client application to the user, because you need to access the api application, so the role of api application is required), then the user can log in to the client application and request a token from the api application. When you obtain the token and use the token to access the API, the API only needs to verify whether the user you log in has the role to access it.
This was a hard nut to crack and is not available in auth0 by default.
You can set roles in the Auth0 id token for OpenId, But you have to write a rule in auth0 dashboard:
Go To Rules section on your Auth0 dashboard and create a new rule:
and then use this code to add the user role claims to the id token, that will be returned in the JWT token claims:
function addRolesToAccessToken(user, context, callback) {
const namespace = 'http://schemas.microsoft.com/ws/2008/06/identity/claims';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`${namespace}/role`] = assignedRoles;
accessTokenClaims[`${namespace}/role`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
return callback(null, user, context);
}
Hope this helps! Cheers

Is there any way to Authenticate B2C application using multiple policies

I am using a B2C login page to authenticate my users. These users have multiple IDP's of their choice as per their business and I created multiple policies with selected IDP's. In the login page based on the user email, I display his login page which has only his relevant IDP's. But in my web application, I can only add one Signup or SignIn policy in my appsettings.json to authenticate the user. Is there any option to have multiple policies in appsettings.json file or any other way to handle this requirement
My Current appsettings.json looks like below
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com/tfp/",
"ClientId": "******-***-****-****-*******",
"Domain": "mycustomdomain.onmicrosoft.com",
"SignUpSignInPolicyId": "Org-signinsignout"
},
You can invoke a different policy for a different type of user by passing the requested policy from a controller method to the authentication middleware:
public IActionResult LogInForBusinessCustomer(string uiLocale)
{
return LogInFor(Constants.AuthenticationSchemes.B2COpenIdConnect, Constants.Policies.SignUpOrSignInWithWorkAccount, uiLocale);
}
public IActionResult LogInForIndividualCustomer(string uiLocale)
{
return LogInFor(Constants.AuthenticationSchemes.B2COpenIdConnect, Constants.Policies.SignUpOrSignInWithPersonalAccount, uiLocale);
}
public IActionResult LogInForPartner(string uiLocale)
{
return LogInFor(Constants.AuthenticationSchemes.B2BOpenIdConnect, null, uiLocale);
}
private IActionResult LogInFor(string authenticationScheme, string policy)
{
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(
authenticationScheme,
new AuthenticationProperties(
new Dictionary<string, string>
{
{Constants.AuthenticationProperties.Policy, policy}
})
{
RedirectUri = Url.Action("LoggedIn", "Account", values: null, protocol: Request.Scheme)
});
}
return RedirectToHome();
}
and then setting the redirection URL for the requested policy in the authentication middleware:
OnRedirectToIdentityProvider = async context =>
{
var policy = context.Properties.Items.ContainsKey(Constants.AuthenticationProperties.Policy) ? context.Properties.Items[Constants.AuthenticationProperties.Policy] : Constants.Policies.SignUpOrSignInWithPersonalAccount;
var configuration = await GetB2COpenIdConnectConfigurationAsync(context, policy);
context.ProtocolMessage.IssuerAddress = configuration.AuthorizationEndpoint;
if (context.Properties.Items.ContainsKey(Constants.AuthenticationProperties.UILocales))
{
context.ProtocolMessage.SetParameter("ui_locales", context.Properties.Items[Constants.AuthenticationProperties.UILocales]);
}
context.ProtocolMessage.SetParameter("dc", "cdm");
context.ProtocolMessage.SetParameter("slice", "001-000");
},
Reference: https://github.com/Azure-Samples/active-directory-external-identities-woodgrove-demo/blob/2b5110c25d1a626bf9b9ac27ecaaabad8b4bccf4/src/WoodGroveGroceriesWebApplication/Startup.cs#L283
Also please check out this thread
Azure B2C - Single App with multiple login for different user types setup in Azure
Hope it helps.

Not allowed users are authentication in my Azure AD Application Multi Tenant

I have two tenants configured in my Azure AD. My users are authentication with successful in my tenant but others users that are another tenant has access in my applications.
What's wrong with my application? I'm using OpenId Connect protocol in my code, for exemple:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
return Task.FromResult(0);
}
}
});
Am I setting something wrong on my Azure?
Someone help me?
Thanks,
Vilela
I have two tenants configured in my Azure AD.
The tenants are corresponding to the Azure Active Directory. So when there are two tenants that means you have two different Azure Active Directory.( refer here about the detail concept)
And to enable the multi-tenat app, we need to enable it from the old Azure portal and locat you app. Then you can set it by refering the figure below:
Update( limit the sepcifc tenants to access the multi-tenants app)
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
// instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// we use this notification for injecting our custom logic
SecurityTokenValidated = (context) =>
{
// retriever caller data from the incoming principal
string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
var issuer1 = "";
var issuer2 = "";
if ((issuer!=issuer1)&& (issuer != issuer2))
// the caller was neither from a trusted issuer - throw to block the authentication flow
throw new SecurityTokenValidationException();
return Task.FromResult(0);
}
}
});

Azure B2C with Web API

The examples I've seen for using Azure B2C with Web API show app.UseOAuthBearerAuthentication (as shown below), however my ASP .NET 5 Web API project uses IApplicationBuilder (not IAppBuilder) and UseOAuthBearerAuthentication does not exist. I've tried app.UseOpenIdConnectAuthentication, however I believe this uses cookies and I couldn't get it working using a Xamarin app as a client. I've tried app.UseWindowsAzureActiveDirectoryBearerAuthentication but I believe this is for standard Azure AD (not B2C) is that true? Any ideas how to get Azure B2C working with the very latest ASP .NET Web API?
Thanks!!!
public void ConfigureAuth(IAppBuilder app)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = clientId,
};
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, "v2.0", discoverySuffix, commonPolicy)))
});
}
This works for me. I hope it helps someone else who is looking to use Azure B2C with the latest .NET Web API framework:
public void ConfigureAuth(IApplicationBuilder app, IOptions<PolicySettings> policySettings)
{
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
MetadataAddress = "https://login.microsoftonline.com/[my-tenant].onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_my-signup-signin-policy",
Audience = "[My-Azure-App-Guid]",
Events = new JwtBearerEvents
{
OnTokenValidated= ctx =>
{
var nameClaim = ctx.AuthenticationTicket.Principal.FindFirst("name");
if (nameClaim != null)
{
var claimsIdentity = (System.Security.Claims.ClaimsIdentity)ctx.AuthenticationTicket.Principal.Identity;
claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, nameClaim.Value));
}
return Task.FromResult(0);
},
OnAuthenticationFailed = ctx =>
{
ctx.SkipToNextMiddleware();
return Task.FromResult(0);
}
}
});
}
If the JWT token is available in your client, then you can use JwtBearerAuthentication in the Web API. The JWT issued by Azure B2C ( social logins) can be used as a token to get authenticated in the Web API.
Refer to https://github.com/sendhilkumarg/AzureB2CWebAppAndAPIAuthentication
In this sample the client is a web app

Resources