I'm currently having issues using swagger to authorize an api call to identity server 4.
My swagger dependency is using swashbuckle version
-beta client object in the identity server 4 looks like
new Client
{
ClientId="swagger",
Enabled = true,
ClientName="Swagger",
AllowedGrantTypes = GrantTypes.Implicit,
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AllowedScopes = new List<string>
{
"apil"
},
RedirectUris = new List<string>
{
"http://localhost:15138/swagger/ui/popup.html"
},
AllowedCorsOrigins = new List<string>
{
"http://localhost:15138",
"http://localhost:15138"
},
AllowAccessTokensViaBrowser = true,
AllowAccessToAllScopes= true
}
The client object is the identity server 4 model
in the configure method for authentication I have this
app.UseIdentityServer();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:15138/",
ScopeName = "apil",
RequireHttpsMetadata = false,
});
Using fiddler my get request looks like this
GET
/connect/authorize?state=9321480892748389&nonce=5279296493808222&client_id=swagger&redirect_uri=http%3A%2F%2Flocalhost%3A15138%2Fswagger%2Fui%2Fpopup.html&response_type=id_token%20token&scope=apil
HTTP/1.1
All the necessary parameters are there, the client has the corresponding client id, but The response I get back is a redirect to an error page with the message of an invalid request. I was anticipating a login page to pass in credentials or something similar to get authorized I was wondering what I did wrong for that to happen.
I ran into this same problem, and it was related to a few different things.
Swagger needs a Security Definition configured.
IdentityServerAuthentication AutomaticAuthenticate needs to be true.
Swagger's client-id and client name need to be configured in Startup.cs.
See below:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "my api title",
Description = "my api desc",
TermsOfService = "None",
Contact = new Contact { Name = "contact" }
});
var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "api.xml");
c.IncludeXmlComments(filePath);
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = "https://url",
Scopes = new Dictionary<string, string>
{
{ "api-name", "my api" }
}
});
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIdentity();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "https://url",
RequireHttpsMetadata = true,
ApiName = "api-name",
ApiSecret = "api-secret",
AllowedScopes = { "api-name", "openid", "email", "profile" },
ClaimsIssuer = "https://url",
AutomaticAuthenticate = true,
});
app.UseStaticFiles();
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger();
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUi(c =>
{
c.ConfigureOAuth2("swagger-name", "swagger-secret", "swagger-realm", "Swagger");
});
}
Related
The Client application makes double queries to a single resource on the server. The first frame has no authorization header and the second frame does. Unfortunately, after reading the first frame, the server does not get the second frame. How to handle it on the ASP.NET CORE 5 server?
Endpoint for testing.
value always = {} when i call from client, from postman everything is working
[ApiExplorerSettings(IgnoreApi = true)]
[HttpPost("Service")]
public IActionResult GetHeader()
{
var value = HttpContext.Request.Headers["Authorization"];
return Ok();
}
app.UseMiddleware<SerilogMiddleware>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/api/socket");
endpoints.UseSoapEndpoint<SVPService.SVPServiceSoap>((options) =>
{
options.Path = "/Service.asmx";
options.Binding = new BasicHttpBinding()
{
TextEncoding = new UTF8Encoding(false),
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Transport = new HttpTransportSecurity() { ClientCredentialType = HttpClientCredentialType.Basic }
}
};
options.SoapSerializer = SoapSerializer.XmlSerializer;
}).RequireAuthorization();
});
app.UseMvc();
Logged Request from client on node.js server to get headers.
First Request Headers
{
'user-agent': 'Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 4.0.30319.42000)',
'content-type': 'text/xml; charset=utf-8',
'content-length': '806',
expect: '100-continue',
connection: 'Keep-Alive'
}
Second Request Headers
{
'user-agent': 'Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 4.0.30319.42000)',
'content-type': 'text/xml; charset=utf-8',
authorization: 'Basic dGVzdG93ZV91c2VybmFtZTp0ZXN0b3dlX3Bhc3N3b3Jk',
'content-length': '806',
expect: '100-continue'
}
Its a my startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy("CorsPolicy", builder =>
{
builder
//.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials().SetIsOriginAllowed(hostName => true);
}));
services.AddQuartz();
services.Configure<JwtAuthentication>(Configuration.GetSection("JwtAuthentication"));
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "xxx",
Title = "xxx",
Description = "xxx",
Contact = new OpenApiContact
{
Name = "xxx",
Email = "xxx",
Url = new Uri("xxx"),
},
});
// Set the comments path for the Swagger JSON and UI.
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
MapperConfiguration mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddSignalR().AddNewtonsoftJsonProtocol();
services.AddSingleton<ITokenService, TokenService>();
services.AddSingleton<IPasswordService, PasswordService>();
services.AddSingleton<IUserProfile, UserProfile>();
services.AddSingleton<IReceiptService, ReceiptService>();
services.AddSingleton<ISend, Send>();
services.AddSingleton<IEncryption, Encryption>();
services.AddSingleton<ParkingTicketManagementServiceV3, TicketManagement>();
services.AddScoped<SVPService.SVPServiceSoap, SVPServiceSoap>();
services.AddScoped<IManageSVP, ManageSVP>();
services.AddScoped<IStripeMethods, StripeMethods>();
services.AddScoped<IManageSchedullerRecurringPayment, ManageSchedullerRecurringPayment>();
services.AddRepository();
services.AddSingleton<IAuthorizationHandler, DenyAnonymousAuthorizationRequirement>();
services.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
options.EnableEndpointRouting = false;
})
.SetCompatibilityVersion(CompatibilityVersion.Latest)
.AddNewtonsoftJson(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver() { NamingStrategy = new LowerCaseNamingStrategy() };
opt.SerializerSettings.StringEscapeHandling = Newtonsoft.Json.StringEscapeHandling.Default;
opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
opt.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
opt.SerializerSettings.MaxDepth = null;
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
services.AddSwaggerGenNewtonsoftSupport();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "StaticFile")),
RequestPath = "/staticfile"
});
app.UseCors("CorsPolicy");
app.UseHttpsRedirection();
app.UseSwagger();
app.UseReDoc(c =>
{
c.SpecUrl = "xxx";
c.DocumentTitle = "xxx";
});
app.UseMiddleware<SerilogMiddleware>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/api/socket");
endpoints.UseSoapEndpoint<SVPService.SVPServiceSoap>((options) =>
{
options.Path = "/Service.asmx";
options.Binding = new BasicHttpBinding()
{
TextEncoding = new UTF8Encoding(false),
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Transport = new HttpTransportSecurity() { ClientCredentialType = HttpClientCredentialType.Basic }
}
};
options.SoapSerializer = SoapSerializer.XmlSerializer;
}).RequireAuthorization();
});
app.UseMvc();
}
}
Just check if the response has the correct headers
Yes,
To answer my question the header was actually missing the WWW-Authenticate: Basic realm = header.
Trying to set up a swagger, and I need to pass two header values. One is the bearer token, and the other one is a tenant id. For some reason, I don't get back the values I put in the Authentication form.
Here is my code but not sure if it is not properly configured or something not working as expected from Swagger.
internal static IServiceCollection AddSwagger(this IServiceCollection collection)
{
collection.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Title",
Version = "v1",
Description = "Description v1."
});
options.AddSecurityDefinition("Authorization", new OpenApiSecurityScheme
{
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
options.AddSecurityDefinition("x-client-accessId", new OpenApiSecurityScheme
{
Name = "x-client-accessId",
In = ParameterLocation.Header,
Scheme = "apiKey",
Type = SecuritySchemeType.ApiKey
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
}] = new List<string>()
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Name = "x-client-accessId",
In = ParameterLocation.Header,
Scheme = "apiKey",
Type = SecuritySchemeType.ApiKey
}] = new List<string>()
});
});
return collection;
}
Configure method:
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Swagger Movies Demo V1");
c.DisplayRequestDuration();
});
app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); });
You need to add the below code in the Configure method also.
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger(c => { c.RouteTemplate = "swagger/{documentName}/swagger.json"; });
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(options =>
{
// specifying the Swagger JSON endpoint.
options.SwaggerEndpoint($"../swagger/{_apiVersion}/swagger.json", $"MyProject API v1");
options.DisplayRequestDuration(); // Controls the display of the request duration (in milliseconds) for "Try it out" requests.
});
I'm trying to get JWT working here, the token is successfully received on my client end after login but when I request user info at the /info route, the authorization fails. Any help would be much appreciated, thanks in advance.
I get the error:
Route matched with {action = "GetInfo", controller = "Accounts", page = ""}. Executing controller action with signature System.Threading.Tasks.Task`1[ProjectConker.Controllers.AccountsInfo] GetInfo() on controller ProjectConker.Controllers.AccountsController (ProjectConker).
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
This is where the token is issued.
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]LoginInfo credentials)
{
if (credentials == null)
{
return BadRequest("Invalid client request");
}
var user = await UserManager.FindByNameAsync(credentials.Username);
await SignInManager.SignInAsync(user, isPersistent: false);
var result = await SignInManager.PasswordSignInAsync(user,
credentials.Password, isPersistent: false, lockoutOnFailure: false);
if (result.Succeeded)
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("**********"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: "http://localhost:5000",
audience: "http://localhost:5000",
claims: new List<Claim>(){
new Claim("username", credentials.Username)
},
expires: DateTime.Now.AddMinutes(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new { Token = tokenString, UserName = user.UserName });
}
else
{
return Unauthorized();
}
}
Save token to local storage
public Login(loginForm : ILoginForm) : Observable<ILoginForm>
{
return this.http.post<ILoginForm>(this.accountsUrl + "/login", loginForm, httpOptions)
.pipe(map<any, any>((data, index) => {
localStorage.setItem("auth_token", data.token);
this.username = data.username;
this.loggedIn = true;
console.log(data);
return data;
}));
}
Gets user information
public GetAccountInfo() : Observable<any>
{
httpOptions.headers.set('Authorization', localStorage.getItem('auth_token'));
return this.http.get(this.accountsUrl + "/info", httpOptions);
}
returns user info, but authorization fails here
[HttpGet]
[Route("info")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<AccountsInfo> GetInfo()
{
var usernameClaim = User.Claims.SingleOrDefault(c => c.Type == "username");
Console.WriteLine(usernameClaim.Value, ConsoleColor.Red);
var user = await UserManager.FindByNameAsync(usernameClaim.Value);
return new AccountsInfo{ DisplayName = user.UserName };
}
My startup.cs
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(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:5000",
ValidAudience = "http://localhost:5000",
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("superSecretKey#345"))
};
});
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddHttpClient();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins("*")
.AllowCredentials();
}));
services.AddSignalR();
services.AddEntityFrameworkSqlServer();
services.AddDbContext<ConkerDbContext>(
options => options.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ConkerDbContext>();
services.AddScoped<SearchEngine>();
services.AddTransient<RoadmapService>();
}
// 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.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/api/chat");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
Since Angular's HttpHeaders are immutable, you can't just use httpOptions.headers.set('Authorization', localStorage.getItem('auth_token'));, because it would have no effect on the original object.
First thing is the header is invalid, use the Bearer method provided by Ashique, then your GetAccountInfo call would look like this:
public GetAccountInfo() : Observable<any> {
const headers = new HttpHeaders({'Authorization': 'Bearer ' + localStorage.getItem('auth_token')});
return this.http.get(this.accountsUrl + "/info", {headers});
}
Here I assumed that you don't have other HttpOptions set, so I'm just passing the header to the HttpClient. Try it this way and let us know if it's still not working.
The default way to add authorization header in HTTP request for ASP.NET core token authentication is to add Bearer before the token. So the code should be like this-
httpOptions.headers.set('Authorization', "Bearer " + localStorage.getItem('auth_token'));
You can override the default behavior to remove the need of Bearer.
Please read the below post to help understand the reason for using bearer before token.
https://www.quora.com/Why-is-Bearer-required-before-the-token-in-Authorization-header-in-a-HTTP-request
Also try this,
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds,
IssuedAt = DateTime.Now,
NotBefore = DateTime.Now
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
What i mean is instead of creating a new JWTSecurityToken, Create a SecuritTokenDescriptor, instead of using function WriteToken, use CreateToken. I have used JWT in this way and it worked.
In the login end point where you create the token you are using key "**********" and in the Setup class you are using key "superSecretKey#345" , this is the problem , the authentication middleware is trying to validate the incoming JWT tokens with key different from the key used to issue the token , you have to use the same key for both issuing and validating , also put the key in somewhere else like "appsettings" to avoid this conflict
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("**********"));
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("superSecretKey#345"))
I have an IdentityServer4 asp.net-core host setup for Resource Owner Password Grant using JWT Bearer tokens and an API in a separate asp.net-core host which has my API and two Angular clients.
The Authentication and Authorization is working from my two Angular clients to my API.
Now I need to expose an API in the IdentityServer4 host so I can create users from one of the Angular clients.
I have copied my Authentication and Authorization setup from my API over to my IdentityServer4 host, however, I cannot get it to Authenticate.
In the below code, within the API, I can set a breakpoint on the jwt.Authority... line and the first call will trigger this breakpoint in my API but not in the IdentityServer4 host.
Authentication
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(jwt =>
{
jwt.Authority = config.Authentication.Authority; //Breakpoint here
jwt.RequireHttpsMetadata = config.Authentication.RequireHttpsMetadata;
jwt.Audience = Common.Authorization.Settings.ServerApiName;
});
Authorization
I'm not sure if it's relevant, but I'm using role based authorization, the following is the setup for this.
var authPolicyBuilder = new AuthorizationPolicyBuilder()
.RequireRole(Common.Authorization.Settings.ServerApiRoleBasePolicyName)
.Build();
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeFilter(authPolicyBuilder));
...
services.AddAuthorization(options =>
{
options.AddPolicy(Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName, policy =>
{
policy.RequireClaim("role", Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName);
I've extracted the following from my logging:
What I see is that in the non-working case, I never get to the point of invoking the JWT validation (#3 in the working logs).
This is just a tiny extract of my logs, I can share them in entirety if needs be.
Working
1 Request starting HTTP/1.1 GET http://localhost:5100/packages/
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
2 Connection id "0HLC8PLQH2NRU" started.
(SourceContext:Microsoft.AspNetCore.Server.Kestrel)
3 Request starting HTTP/1.1 GET http://localhost:5000/.well-known/openid-configuration
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
--Truncated--
Not Working
1 Request starting HTTP/1.1 GET http://localhost:5000/users
(SourceContext:Microsoft.AspNetCore.Hosting.Internal.WebHost)
--Truncated--
Clients
new Client
{
ClientId = "setup_app",
ClientName = "Setup App",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 3600,
IdentityTokenLifetime = 3600,
UpdateAccessTokenClaimsOnRefresh = true,
SlidingRefreshTokenLifetime = 3600,
AllowOfflineAccess = false,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AlwaysSendClientClaims = true,
Enabled = true,
RequireConsent = false,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedCorsOrigins = { config.CorsOriginSetupClient },
ClientSecrets =
{
new Secret(Common.Authorization.Settings.ServerApiSetupClientSecret.Sha256())
},
AllowedScopes =
{
Common.Authorization.Settings.ServerApiName,
}
},
new Client
{
ClientId = "client_app",
ClientName = "Client App",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 3600,
IdentityTokenLifetime = 3600,
UpdateAccessTokenClaimsOnRefresh = true,
SlidingRefreshTokenLifetime = 3600,
AllowOfflineAccess = false,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AlwaysSendClientClaims = true,
Enabled = true,
RequireConsent = false,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedCorsOrigins = { config.CorsOriginSetupClient },
ClientSecrets =
{
new Secret(Common.Authorization.Settings.ServerApiAppClientSecret.Sha256())
},
AllowedScopes =
{
Common.Authorization.Settings.ServerApiName,
}
}
IdentityResources
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource(Common.Authorization.Settings.ServerApiScopeName, new []{
"role",
Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName,
Common.Authorization.Settings.ServerApiAppClientAdminRolePolicyName,
Common.Authorization.Settings.ServerApiAppClientUserRolePolicyName,
}),
};
User
var adminUser = new ApplicationUser
{
UserName = "admin",
Email = "admin#noreply",
};
adminUser.Claims = new List<IdentityUserClaim>
{
new IdentityUserClaim(new Claim(JwtClaimTypes.PreferredUserName, adminUser.UserName)),
new IdentityUserClaim(new Claim(JwtClaimTypes.Email, adminUser.Email)),
new IdentityUserClaim(new Claim("role", Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName)),
new IdentityUserClaim(new Claim("role", Common.Authorization.Settings.ServerApiRoleBasePolicyName)),
new IdentityUserClaim(new Claim("profileImage", $"https://robohash.org/{Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(System.Text.Encoding.UTF8.GetBytes(adminUser.UserName)))}?set=set2"))
};
adminUser.AddRole(Common.Authorization.Settings.ServerApiSetupClientAdminRolePolicyName);
API
new ApiResource(Common.Authorization.Settings.ServerApiName, "Server API"){
ApiSecrets =
{
new Secret(Common.Authorization.Settings.ServerApiAppClientSecret.Sha256())
},
},
Look up here https://github.com/IdentityServer/IdentityServer4.Samples
Seems like it should be like:
Authentication:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = config.Authentication.Authority;
options.RequireHttpsMetadata = false;
options.ApiName = ServerApiName;
options.ApiSecret = ServerApiAppClientSecret;
});
Or with JWT you can try like:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = config.Authentication.Authority;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudiences = new[]
{
$"{config.Authentication.Authority}/resources",
ServerApiName
},
};
});
Also, you will able to add authorization policy, like:
Authorization:
services.AddMvc(opt =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireScope("api").Build();
opt.Filters.Add(new AuthorizeFilter(policy));
})
There is a MS out of the box service designed to add support for local APIs
First in IDS4 startup.cs, ConfigureServices add
services.AddLocalApiAuthentication();
Then in IDS4 config.cs in your client declaration
AllowedScopes = {
IdentityServerConstants.LocalApi.ScopeName, <<<< ---- This is for IDS4 Api access
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
...
Still in config add the new scope to ApiScopes
new ApiScope(IdentityServerConstants.LocalApi.ScopeName)
Note
IdentityServerConstants.LocalApi.ScopeName resolves to 'IdentityServerApi'
Now in your shiny new Api located in the IDS4 project add the authorize tag to your api endpoint
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Finally you need to request this new scope in your Client
Scope = "openid sig1 api1 profile email offline_access company IdentityServerApi",
Thats it
I think this could help, it's included in the community samples.
We have a machine running IdentityServer4 which itself is used as a federated gateway to Office 365.
I'm trying to figure out how to circumvent this scenario: we create an MVC-app that has the following owin-startup class:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
ExpireTimeSpan = TimeSpan.FromMinutes(1),
SlidingExpiration = false
});
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "https://localhost:5000/",
ClientId = "dev",
RedirectUri = "http://localhost:54509/",
ResponseType = "id_token token",
Scope = "openid profile account",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
var claims_to_keep =
n.AuthenticationTicket.Identity.Claims
.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
var ci = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
"name", "role");
ci.AddClaims(claims_to_keep);
n.AuthenticationTicket = new AuthenticationTicket(
ci, n.AuthenticationTicket.Properties
);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var id_token = n.OwinContext.Authentication.User.FindFirst("id_token")?.Value;
n.ProtocolMessage.IdTokenHint = id_token;
}
return Task.FromResult(0);
}
}
});
app.UseStageMarker(PipelineStage.Authenticate);
So far so good: upon first visiting the user is redirected to the Identity Server, signing in and getting back but when they lose their token or become unauthenticated and try to access a controller decorated with [Authorize] the plain HTML of the sign-on site is returned to that controller.
Are we using the wrong flow for this back channel, server-to-server communication? Or is there a way to intercept this so that they can be redirected immediately before the controller freaks out over getting HTML instead of JSON.