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.
Related
I am to build an ASP.NET Core webapp with .NET6.
I have followed the Azure-Sample repository on GitHub
I have adjusted the appsettings.json with AzureAd section:
"AzureAd": {
"ClientId": "my-client-guid",
"TenantId": "my-tenant-guid",
"Domain": "my-tenant-name",
"ClientSecret": "my-client-secret-guid",
"Instance": "https://login.microsoftonline.com/",
"Scopes": "user.read,profile,openid",
"ClientCapabilities": [ "cp1" ],
"CallbackPath": "/signin-oidc"
}
ClientId and TenantId fields are fed from Azure Portal AD Application Blade , where I have created the App Registration. On
the detail page of this application is the Overview blade - which
gives reference to ClientId, TenantId.
ClientSecret has been created at the same page, at Certificates & secrets blade.
Domain is provided from Azure Domain list blade
User roles (Reader/Admin) have been created at the above (Azure AD Application) page.
Still on the same page:
Authentication Tab a new Web platform was added with Redirect URI https://localhost:7293/signin-oidc. As for Front-Channel logout https://localhost:44321/signout-oidc was provided.
ID Tokens is ticked under Implicit grant and hybrid flows section
Single Tenant option (only allow organization accounts)
Public flows disabled
In Enterprise Applications my application is listed. In the details view of the application:
Properties tab: Assignment required is set to false. I do not want to preassign users to the app
Visible to users: set to false
Users and groups tab: the Security groups have been created for which the Roles (Reader/Admin) are assigned to. Members are assigned to Security groups. For testing purposes I have added my Azure AD User as well with Reader role.
Single Sign-On: Here I see the following message: The single sign-on configuration is not available for this application in the Enterprise applications experience. {{my-application-name}} was created using the App registrations experience
Sign-in logs: oddly enough I see here my logins, so I assume that the role claims are not transferred to my WebApplication, since I get Unauthorized error upon opening the view at the controller.
The WebApp references
Config sections I have mapped to a model so I can inject if needed as IOptions
MicrosoftGraphConfiguration to map GraphApiUrl:
public class MicrosoftGraphConfiguration
{
public const string ConfigurationName = "MicrosoftGraph";
public string? GraphApiUrl { get; set; }
}
AzureAdConfiguration to map AzureAd configurations
public class AzureAdConfiguration
{
public const string ConfigurationName = "AzureAd";
public string? ClientId { get; set; }
public string? TenantId { get; set; }
public string? ClientSecret { get; set; }
public string? Domain { get; set; }
public string? Instance { get; set; }
public string? BaseUrl { get; set; }
public string[]? ClientCapabilities { get; set; }
public string[]? Scopes { get; set; }
public string? CallbackPath { get; set; }
}
Program.cs to add configure and add services to the DI container, etc.
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MicrosoftGraphConfiguration>(builder.Configuration.GetSection(MicrosoftGraphConfiguration.ConfigurationName));
builder.Services.Configure<AzureAdConfiguration>(builder.Configuration.GetSection(AzureAdConfiguration.ConfigurationName));
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "User.Read" })
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AssignmentToReaderRoleRequired", policy => policy.RequireRole("Reader"));
options.AddPolicy("AssignmentToAdminRoleRequired", policy => policy.RequireRole("Admin"));
});
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
});
// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Controller actions for testing purposes:
[HttpGet]
public async Task<IActionResult> IndexAsync()
{
GetTenantDetailsResult tenantDetails = new();
if (this.tenantService != null)
{
tenantDetails = await this.tenantService.GetTenantDetailsAsync();
}
return View(new TenantViewModel(tenantDetails));
}
[HttpGet]
[Route("/Reader")]
[Authorize(Policy = AuthorizationPolicies.AssignmentToReaderRoleRequired)]
public IActionResult Reader()
{
var asd = User;
return Ok("He is a reader");
}
[HttpGet]
[Route("/Admin")]
[Authorize(Policy = AuthorizationPolicies.AssignmentToAdminRoleRequired)]
public IActionResult Admin()
{
return Ok("He is an admin");
}
Launching the app (without cookies - incognito) redirects me to
https://login.microsoft.com/{my-tenantId}/oauth2/authorize?.... Once logged in, endpoints without the [Authorize(...)] attribute works well.
Opening any of the above endpoints (where [Authorize] attribute is in place) gives me HTTP 404 and redirects to:
https://localhost:7293/MicrosoftIdentity/Account/AccessDenied?ReturnUrl=%2Freader
According to the docs, the Roles Claim should be sent together with any ID Tokens.
"These assigned app roles are included with any token that's issued for your application, either ... or ID tokens when your app is signing in a user"
I am unsure -thus my questions are- if:
The roles claim are not sent?
The roles claim are sent but cannot be parsed
How to proceed with the debugging to find out the issue
As a weird behavior.. if I open the application/Reader endpoint in Incognito/InPrivate browser window, I get a HTTP 500 response code after the login with the following error details:
MsalServiceException: A configuration issue is preventing authentication - check the error message from the server for details. You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS7000215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app
Even though the ClientSecret is as above mentioned, I believe it is correct.
A reason for the Http 500 might be that currently I do not maintain an endpoint for https://localhost:7293/signin-oidc
I appreciate helps, as I am a bit lost here for 3rd day.
Thx
I tried to reproduce the same in my environment and got below results:
I registered one Azure AD application named WebApp-RolesClaims and followed the same steps mentioned on GitHub
I have adjusted the appsettings.json file with same values as you like below:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "my-tenant-name",
"TenantId": "my-tenant-guid",
"ClientId": "my-client-guid",
"CallbackPath": "/signin-oidc"
"SignedOutCallbackPath ": "/signout-callback-oidc",
"Scopes": "user.read,profile,openid",
// To call an API
"ClientSecret": "my-client-secret-guid"
}
Now I ran the sample, and it took me to Microsoft login page.
When I signed in with Azure AD user credentials, I got consent screen like below:
After accepting the above consent, it gave me same exception as you like below:
As the error message says, it usually occurs if you include Client secret ID instead of Client secret value.
To resolve the error, you need replace "ClientSecret" with below:
Note that, key value will be visible only for few seconds after secret's creation and you cannot retrieve it later.
If that's the case, delete the existing secret and create new secret to copy it's value like below:
I have adjusted the appsettings.json file by replacing Client Secret value as below:
When I ran the sample now, I got the claims of signed in user including roles like below:
So, try changing Client Secret with key value in your appsettings.json file .
I have an ASP.NET Core web app that is authenticating with Azure AD in a multi-tenant configuration using Microsoft.Identity.Web. We use a tenant/company identifier as the subdomain of our apps URL. (companyA.myapp.com, companyB.myapp.com). Some users have access to more than one tenant of the application, so we cannot map a Azure AD tenant directly to a single tenant/company in our app.
With Microsoft.Identity.Web, how is the state parameter set or manipulated as described here? I would like to follow the guidance provided here, but am not sure where to start.
https://learn.microsoft.com/en-us/azure/active-directory/develop/reply-url#use-a-state-parameter
If you have several subdomains and your scenario requires that, upon successful authentication, you redirect users to the same page from which they started, using a state parameter might be helpful.
In this approach:
Create a "shared" redirect URI per application to process the security tokens you receive from the authorization endpoint.
Your application can send application-specific parameters (such as subdomain URL where the user originated or anything like branding information) in the state parameter. When using a state parameter, guard against CSRF protection as specified in section 10.12 of RFC 6749).
The application-specific parameters will include all the information needed for the application to render the correct experience for the user, that is, construct the appropriate application state. The Azure AD authorization endpoint strips HTML from the state parameter so make sure you are not passing HTML content in this parameter.
When Azure AD sends a response to the "shared" redirect URI, it will send the state parameter back to the application.
The application can then use the value in the state parameter to determine which URL to further send the user to. Make sure you validate for CSRF protection.
Here is how I eventually solved the the MS Login infinite redirects with the tenant per subdomain scheme problem. (Trying to come up with a better name for the problem. š)
Provide a SigninRedirect controller action that accepts a returnUrl parameter that we must validate to avoid being an Open Redirect.
Example URL with returnUrl set to companyA.example.com/foo&bar=1:
https://signin.example.com/signin-redirect?returnUrl=companyA.example.com%2Ffoo%26bar%3D1
[Route("")]
public class SigninController : Controller
{
private readonly IMediator _mediator;
private readonly IConfiguration _configuration;
public SigninController(IMediator mediator, IConfiguration configuration)
{
_mediator = mediator;
_configuration = configuration;
}
[Authorize]
[HttpGet("/signin-redirect")]
public IActionResult SigninRedirect(string returnUrl)
{
string redirect;
if (!string.IsNullOrEmpty(returnUrl) && IsValidSubdomainUrl(returnUrl))
{
redirect = returnUrl;
}
else
{
var appHost = _configuration.GetValue<string>("General:ApplicationHost");
var home = new UriBuilder("https", appHost).Uri;
return Redirect(home.AbsoluteUri);
}
return Redirect(redirect);
}
/// <summary>
/// Avoid Open Redirect Vulnerability
/// https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
private bool IsValidSubdomainUrl(string returnUrl)
{
var appHost = _configuration.GetValue<string>("General:ApplicationHost");
var isValid = Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute) &&
Uri.TryCreate(returnUrl, UriKind.Absolute, out var uri) &&
uri?.Host.EndsWith(appHost) == true;
return isValid;
}
}
In ConfigureServices(IServiceCollection services) set cookies to be shared across all the subdomains and configure an OnRedirectToIdentityProvider event to redirect to your signin URL when the user is not yet authenticated:
var cookieDomain = _configuration.GetValue<string>("General:CookieDomain");
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(3);
options.Cookie.Domain = cookieDomain;
options.Cookie.Path = "/";
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
_configuration.Bind("AzureAd", options);
options.Events ??= new OpenIdConnectEvents();
options.Events.OnRedirectToIdentityProvider += context => {
var originalRequestUri = context.HttpContext.Request.GetUri();
var signInHost = _configuration.GetValue<string>("General:SignInHost");
var signInPath = _configuration.GetValue<string>("General:SignInUrl");
if (!originalRequestUri.Host.Equals(signInHost, StringComparison.InvariantCultureIgnoreCase))
{
var signInUrl = QueryHelpers.AddQueryString(signInPath, "returnUrl", originalRequestUri.AbsoluteUri);
// When on a subdomain and not authorized, then redirect to
// our signin URL.
context.Response.Redirect(signInUrl);
// Let Microsoft.Identity.Web know that we already handled
// this redirect
context.HandleResponse();
}
return Task.CompletedTask;
};
},
cookieOptions =>
{
cookieOptions.Cookie.Domain = cookieDomain;
cookieOptions.Cookie.Path = "/";
cookieOptions.Cookie.SameSite = SameSiteMode.Lax;
})
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read" })
.AddDistributedTokenCaches();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Domain = cookieDomain;
options.Cookie.Name = ".AspNet.SharedCookie";
options.Cookie.Path = "/";
options.Cookie.SameSite = SameSiteMode.Lax;
});
Configuration defined:
"General:CookieDomain": ".example.com",
"General:ApplicationHost": "example.com",
"General:SignInHost": "signin.example.com",
"General:SignInUrl": "https://signin.example.com/signin-redirect"
In addition, you will need the usual AzureAd configuration section from the Microsoft Docs for Azure AD.
"AzureAd:Instance": "https://login.microsoftonline.com/",
"AzureAd:Domain": "...",
"AzureAd:TenantId": "common",
"AzureAd:ClientId": "...",
"AzureAd:ClientSecret": "...",
"AzureAd:CallbackPath": "/signin-oidc",
"AzureAd:SignedOutCallbackPath": "/signout-callback-oidc",
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
I would like to implement multiple sign up/sign in policies in Azure AD B2C similar to this question but I don't know how to configure my solution in Visual Studio to reference the different signup policies specified in the web.config file. Can anyone help please?
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 LogInForIndividualCustomer()
{
return LogInFor(Constants.AuthenticationSchemes.B2COpenIdConnect, Constants.Policies.SignUpOrSignInWithPersonalAccount);
}
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;
}
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");
});