I am trying to implement AuthorizationCode grant type for SwaggerUI that will work with Azure AD.
The error that I am getting is:
Content Security Policy: The page’s settings blocked the loading of a
resource at
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
(“connect-src”)
I used web debugging proxy tool to check all network requests and I see there is a request to my redirect uri with state and authorization code that should be used for token exchange, but that doesn't happen.
My SwaggerUI setup:
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.OAuthClientId(Configuration["Swagger:ClientId"]);
c.OAuthAppName("My API");
c.OAuthUsePkce();
c.OAuthScopeSeparator(" ");
c.InjectStylesheet("/swagger-ui/dark.css");
c.DisplayRequestDuration();
});
...
string authUrl = $"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/oauth2/v2.0/authorize";
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "OAuth2.0 Auth Code with PKCE",
Name = "oauth2",
Type = SecuritySchemeType.OAuth2,
In = ParameterLocation.Header,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(authUrl, UriKind.Absolute),
TokenUrl = new Uri(authUrl.Replace("authorize", "token"), UriKind.Absolute),
Scopes = new Dictionary<string, string>
{
{ $"api://{Configuration["AzureAD:ResourceId"]}/user_impersonation", "user impersonation" }
}
}
}
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
Array.Empty<string>()
}
});
Related
I could authenticate swagger on Azure AD B2C. It works fine on http://localhost:5078/ . But as soon as I push my website to azure I can not authenticate swagger. It still uses localhost:5078 as redirect URL and not azure website URL.
My code:
builder.Services.AddSwaggerGen(c => {
var OATH2 = "oauth2";
// Enabled OAuth security in Swagger. From here: https://stackoverflow.com/questions/66894523/swagger-not-able-to-authenticate-to-azure-ad-b2c
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = OATH2
}
},
new List<string>()
}
});
c.AddSecurityDefinition(OATH2, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{builder.Configuration["AzureAdB2C:Instance"]}/{builder.Configuration["AzureAdB2C:Domain"]}/{builder.Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"{builder.Configuration["AzureAdB2C:Instance"]}/{builder.Configuration["AzureAdB2C:Domain"]}/{builder.Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/oauth2/v2.0/token"),
Scopes = new Dictionary<string, string>() { { builder.Configuration["AzureAdB2C:Scope"], "backend all" } }
}
}
});
});
....
app.UseSwagger();
app.UseSwaggerUI(c => {
c.OAuthClientId(builder.Configuration["AzureAdB2C:SwaggerClientId"]);
c.OAuthScopes(builder.Configuration["AzureAdB2C:Scope"]);
c.OAuthUsePkce();
});
OK on localhost:
Not OK on azure:
https://anna-carat-auth-test.azurewebsites.net/swagger/index.html
Both redirect URLs are registered in app:
How to set a proper redirect URL for swagger?
Created a sample Web Api application.
In Visual Studio click on connected services and add a dependency of Microsoft Identity platform as shown in below snippet.
Once the dependencies are added then in Program.cs file is updated with Azure AD related settings.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
// It added the Authentication and Authorization methods.
app.UseAuthentication();
app.UseAuthorization();
In Program.cs file made the code changes as shown below.
builder.Services.AddSwaggerGen(
x =>
{
x.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "Swagger Azure AD Demo",
Version = "v1"
});
x.AddSecurityDefinition("oauth2", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "Oauth2.0 which uses AuthorizationCode flow",
Name = "oauth2.0",
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(builder.Configuration["SwaggerAzureAD:AuthorizationUrl"]),
TokenUrl = new Uri(builder.Configuration["SwaggerAzureAD:TokenUrl"]),
Scopes = new Dictionary<string, string>
{
{builder.Configuration["SwaggerAzureAD:Scope"], "Access API as User" }
}
}
}
});
x.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference= new OpenApiReference{Type=ReferenceType.SecurityScheme,Id="oauth2"}
},
new[]{builder.Configuration["SwaggerAzureAD:Scope"]}
}
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(x=>
{
x.OAuthClientId(builder.Configuration["SwaggerAzureAD:ClientId"]);
x.OAuthUsePkce();
});
}
And the AppSettings.Json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "Something.onmicrosoft.com",
"TenantId": "TenantID",
"ClientId": "ClientID",
"CallbackPath": "/signin-oidc",
"Scopes": "access_as_user"
},
// These values need to be copied from App registartion
"SwaggerAzureAD": {
"AuthorizationUrl": "",
"TokenUrl": "",
"ClientId": "",
"Scope": ""
}
}
Expose API from App registration.
API Permissions need to be given in the App registrations of Azure.
And has to be copied the value and need to use in the scope attribute of AppSettings.json
And redirection urls to be mentioned and to be used in AppSettings.json form App registration -> Authentication as shown in below snippet.
And able to get the response without any issues.
Response:
Net core Web API project. I registered app in azure AD for Web API app. I configured swagger and I registered one more app in Azure AD.
I am trying to do authorization on my webapis based on groups. In appsettings.json I have all the values.
Below is my startup looks like.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = azureActiveDirectoryOptions.Authority;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new List<string>
{
azureActiveDirectoryOptions.AppIdUri,
azureActiveDirectoryOptions.ClientId
},
ValidateIssuer = true
};
});
services.AddScoped<IAuthorizationHandler, GroupsCheckHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("GroupsCheck", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new GroupsCheckRequirement("2a39995a-8fd1-410e-99e2-11cf6046090d"));
});
});
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = swaggerUIOptions.AuthorizationUrl,
TokenUrl = swaggerUIOptions.TokenUrl
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new[] { "readAccess", "writeAccess" } }
});
});
}
// 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.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(swaggerUIOptions.ClientId);
c.OAuthClientSecret(swaggerUIOptions.ClientSecret);
c.OAuthRealm(azureActiveDirectoryOptions.ClientId);
c.OAuthAppName("Swagger");
c.OAuthAdditionalQueryStringParams(new { resource = azureActiveDirectoryOptions.ClientId });
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseAuthentication();
app.UseMvc();
}
When I run the application using https://localhost:44319/swagger
Now I have Authorize button in my swagger. Whenever I try to Authorize, It will ask me to enter user name and password. Authentication works as expected. Next I want to hit /api/values/users/{id}. the controller looks below.
[Authorize(Policy = "GroupsCheck")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}
I need group based authorization. In startup I have added policy.
services.AddAuthorization(options =>
{
options.AddPolicy("GroupsCheck", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new GroupsCheckRequirement("2a39995a-8fd1-410e-99e2-11cf6046090d"));
});
});
Below is my GroupsCheckHandler.cs
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
GroupsCheckRequirement requirement)
{
GraphServiceClient client = await MicrosoftGraphClient.GetGraphServiceClient();
//Tried to get user and dint work for me
var user = await client.Me.Request().GetAsync();
//Here exception occurs
var groupList = await client.Groups.Request().GetAsync();
var result = false;
foreach (var group in groupList)
{
if (requirement.groups.Equals(group.Id))
{
result = true;
}
}
if (result)
{
context.Succeed(requirement);
}
}
Below is my MicrosoftGraphClient.cs
public static async Task<GraphServiceClient> GetGraphServiceClient()
{
// Get Access Token and Microsoft Graph Client using access token and microsoft graph v1.0 endpoint
var delegateAuthProvider = await GetAuthProvider();
// Initializing the GraphServiceClient
graphClient = new GraphServiceClient(graphAPIEndpoint, delegateAuthProvider);
return graphClient;
}
private static async Task<IAuthenticationProvider> GetAuthProvider()
{
AuthenticationContext authenticationContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
// ADAL includes an in memory cache, so this call will only send a message to the server if the cached token is expired.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResource, clientCred).ConfigureAwait(false);
var token = authenticationResult.AccessToken;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token.ToString());
return Task.FromResult(0);
});
return delegateAuthProvider;
}
Now Whenever I start hitting to my api am getting exception in groupshandler.cs
Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
I have added Microsoft graph permission to my app in azure AD. I thing to read groups we need Admin consent. Here I am struggling. Below permission I can see under enterprise application in azure ad under user consent tab.
Below is token format generated through authenticationContext.AcquireTokenAsync method
Other side this token also looks strange to me and this is missing many fields.
Now someone please help me what wrong steps I have done in the above implementation. Can someone help in this regard. Any help would be very helpful for me. Thanks a lot
You were using client credential to get the access Token. So you need to add application permissions(not delegated permissions) on Azure portal.
After adding the application permissions, you also need to grant admin consent.
following Issue 1 and Issue 2 I cant sign out from IS4. Local ASP.NET cookies are not removed and I cannot re-login to IS4.
It works for Core MVC app, but for MVC5 does not.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "TRUX.NET",
Authority = baseAddress,
RedirectUri = $"http://localhost:2510/signin-oidc",
PostLogoutRedirectUri = $"http://localhost:2510/signout-callback-oidc",
ResponseType = "code id_token",
Scope = "openid profile offline_access custom.profile AuthorizationWebApi Common.WebApi",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
--------
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
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);
}
Server config
new Client
{
ClientId = "MVC50",
ClientName = "MVC50 APP",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets= {new Secret("123456".Sha256()) },
RedirectUris = { "http://localhost:2510/signin-oidc" },
PostLogoutRedirectUris = {"http://localhost:2510/signout-callback-oidc"},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.profile",
"AuthorizationWebApi",
"Common.WebApi"
},
AllowOfflineAccess = true
}
If any one can help, i'll appreciate.
regards.
Update :
I removed signout-callback-oidc from both server and client configs.
My signout action is this one
public async Task<ActionResult> SignOut()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
Undeterministically I can call end session endpoint. The logs are given below.
id_token_hint is not null
|1|Microsoft.AspNetCore.Hosting.Internal.WebHost|INFO|Request starting HTTP/1.1 OPTIONS http://www.abcdef.com:5000/connect/endsession?post_logout_redirect_uri=http%3a%2f%2flocalhost%3a2510%2f&id_token_hint=(hint)
|1|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|DEBUG|OPTIONS requests are not supported
|0|IdentityServer4.CorsPolicyProvider|DEBUG|CORS request made for path: /connect/endsession from origin: http://localhost:2510 but rejected because invalid CORS path
|9|Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware|DEBUG|AuthenticationScheme: Identity.Application was not authenticated.
|0|IdentityServer4.Hosting.EndpointRouter|DEBUG|Request path /connect/endsession matched to endpoint type EndSession
|0|IdentityServer4.Hosting.EndpointRouter|DEBUG|Mapping found for endpoint: EndSession, creating handler: IdentityServer4.Endpoints.EndSessionEndpoint
|0|IdentityServer4.Hosting.IdentityServerMiddleware|INFO|Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionEndpoint for /connect/endsession
|0|IdentityServer4.Endpoints.EndSessionEndpoint|WARN|Invalid HTTP method for end session endpoint.
|0|IdentityServer4.Hosting.IdentityServerMiddleware|TRACE|Invoking result: IdentityServer4.Endpoints.Results.StatusCodeResult
|9|Microsoft.AspNetCore.Server.Kestrel|DEBUG|Connection id "0HL3SPFTNB2FH" completed keep alive response.
|2|Microsoft.AspNetCore.Hosting.Internal.WebHost|INFO|Request finished in 1322.3713ms 405
I configured the AppBuilder to use OpenIdConnectAuthentication and I added the [Authorize] header on the endpoint I want the authentication to be applied.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = ClientId,
Authority = Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
await Task.FromResult(0);
}
}
});
How can I send the variable code back to the controller? Here there is no HttpContext, and the OwinContext I have access token is different than the one in the controller.
I'm currently working on a project where I have a Web API that uses the Graph API to create accounts in my Azure AD and put them in the correct group as well.
Next to that I have several API calls exposed that will be called by a native iOS app as well as an angularJS web app. The client has concocted some weird way of doing the authentication because he firmly believes his users to be utter morons.
The client is handing custom prepped iOS devices to certain people. The device has an Azure AD User(principal) and password. Once they are handed out, some process on the angularJS web app does this, the app will then call my token controller that looks like this:
public async Task<string> GetTokenForUserAsync(string userPrincipalName, string password)
{
var uri = string.Format("{0}/oauth2/token", AuthString);
using (var httpClient = new HttpClient
{
DefaultRequestHeaders = { Accept = { new MediaTypeWithQualityHeaderValue("application/json") } }
})
{
var values = new Dictionary<string, string>
{
{"resource", GraphUrl },
{"client_id", ClientId },
{"client_secret", ClientSecret },
{"grant_type", "password" },
{"username", userPrincipalName },
{"password", password },
{"scope", "openid" }
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync(uri, content);
var responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
The passed parameters are, not 100% exact, but very alike:
AuthString : "https://login.microsoftonline.com/myAzureAD.onmicrosoft.com"
GraphUrl : "https://graph.windows.net"
ClientId : My Web API ClientId (a guid)
ClientSecret : My Web API Client Secret (2year valid access key)
So this call actually provides me with an access_token. The problem that I'm having is that the tokens I get are never authorized. I've tried several Startup setups, but none are working.
Currently I've got the following code in my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:ClientId"]
}
});
//app.UseWindowsAzureActiveDirectoryBearerAuthentication(
// new WindowsAzureActiveDirectoryBearerAuthenticationOptions
// {
// TokenValidationParameters = new TokenValidationParameters
// {
// ValidAudience = ConfigurationManager.AppSettings["ida:ClientId"]
// },
// Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
// });
}
It's the first time that I'm working with Azure and Active Directory. So I'm probable doing something really stupid. At this moment in time I don't care much about styling and 'the right way'. I just want this thing to work :,/
Hope I described my problem correctly and documented my question accordingly. If you have any questions, please don't hesitate to ask!
Thanks a bunch in advance
I dont think you can get accesstoken from var values = new Dictionary
{
{"resource", GraphUrl },
{"client_id", ClientId },
{"client_secret", ClientSecret },
{"grant_type", "password" },
{"username", userPrincipalName },
{"password", password },
{"scope", "openid" }
};
so you could try other methods:
AuthenticationContext aContext =
new AuthenticationContext("https://login.microsoftonline.com/tenantid");
AuthenticationResult aResult =
aContext.AcquireToken("https://graph.windows.net",
"1950a258-227b-4e31-a9cf-717495945fc2",
new UserCredential(UserId, PasswordId));
string result = string.Empty;
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", aResult.AccessToken);
HttpResponseMessage response =
httpClient.GetAsync("https://graph.windows.net/tenantid/users/userid?api-version=1.6").Result;
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}
Console.WriteLine(result);
Console.ReadLine();