I am using Spring Boot + Azure AD + angular9 and using azure ad default provided api to get access to my application.
After launching application Microsoft login appear -> verify creds -> show me my landing page. All api works fine.
But after sometime (almost 5-6 mins) all api start failing.
It showing CORS ERROR in browser console.
Access to XMLHttpRequest at 'https://login.microsoftonline.com/common/oauth2/authorize?response_type=code
&client_id=<client_id>
&scope=openid%20https://graph.microsoft.com/user.read&state=lZfrMwK0nx5kQDjO56DJsMTFhcuvDmU5o0-ZCvTPvI4%3D
&redirect_uri=https://{abc}.com/login/oauth2/code/azure&nonce=CCGwfpb4klAiPtEApCs3nS8ICod0-htdHWvBATNYfXs'
(redirected from 'https://{abc}.com/api/supplier/add') from origin
'https://{abc}.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
for every api onwards it is showing CORSS ERROR
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://login.microsoftonline.com/common/oauth2/authorize?
response_type=code&client_id=<client_id>
&scope=openid%20https://graph.microsoft.com/user.read
&state=LAJfYfn9XFOISAHO4Cq4iA5_Dkya3CFDXKgQmQVfpxg%3D
&redirect_uri=https://{abc}.com/login/oauth2/code/azure
&nonce=IBF8nVnrWhH-SY9VpNxouZcxn_6JZEo3J_d-JBHTAK4.
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
Here is my Web-Security-config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${logout.url}")
String logoutUrl;
#Value("${redirect.url}")
String redirectUrl;
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
String logoffUrl = logoutUrl+"?post_logout_redirect_uri="+URLEncoder.encode(redirectUrl, "UTF-8");
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
http.logout().logoutUrl("/api/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(logoffUrl) ;
}
}
WebMvcConfig
#Configuration
public class WebMvcConfig implements WebMvcConfigurer{
#Value("${spring.allowed.origin}")
public String allowedOrigin;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigin)
.allowedMethods("GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
From angular side I am sending header like below
headers = new HttpHeaders({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Credentials': 'true',
});
Please help.. thanks
I implemented SPA using angular msal service.
I use loginPopUp method to get login pop up provided by Microsoft. It solve my ADFS security issue also.
Here I am not using any accessTokenSilent / accessTokenRedirect method to get access token after the previous token expire.
I added CSP header in my nginx.conf file (web server conf file), so after the token expire it automatically providing me new access token on click of any api.
header for CSP added in nginx.conf file
add_header Content-Security-Policy "frame-ancestors 'self';" always;
Related
I'm looking at the security model of a website that's being developed. After researching the web i have found that there are several security models to secure websites i.e. Basic Auth, JWT ...
At the moment, SSL is not enabled as still in dev. Website has a login page and communicates via API's (including login and logout). On the login page, as a test, I have attempted to log in with false details, and then I have looked at the developer tools to identify the security mechanism and found the following screenshots. I think the site is using basic authentication, though I noted that the email / password is not encoded and is using a custom login form. Could someone confirm if it is basic authentication being utilised?
Developer Tools Images
[Request Header Image][2]
UPDATE:
I discovered that once the user is authenticated by email/password, I should have posted the screenshots as this is where keys are returned. In the below screenshot a bidder token and bidder secret is sent back to client. I think these are generated through crypto on backend. So I don't think its JWT, but is this a suitable way in creating keys and not sending in header but in response body?
Network tab after user logged in
Login Form Code :
{
/* prepare ui */
progress.classList.remove('hide');
login_btn.innerText = 'Logging In';
login_btn.setAttribute('disabled', true);
/* make http request */
var http = new XMLHttpRequest();
var url = SERVER + '/api/bidder/login';
var body = {
email: email.value,
password: password.value
};
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/JSON');
http.onreadystatechange = function () { //Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) {
var res = JSON.parse(http.responseText);
if (res.status) {
localStorage.setItem("bidData", JSON.stringify(res.data));
window.location.href = window.location.href.replace('login.html','');
} else {
Toast.show('Danger', res.message);
}
/* reset ui */
progress.classList.add('hide');
login_btn.innerText = 'Log In';
login_btn.removeAttribute('disabled');
}
}
http.send(JSON.stringify(body));
}
When you use basic access authentication, credentials wouldn't be loaded in a request payload. They reside in an authorization header like "Authorization: Basic ~some credential here~".
So if you neither see this authorization header in your request nor a popup like below on the website, basic access authentication is not enabled.
Spring security is the most basic authentication in the Spring project
If you want to enable Spring security, the first thing you must add is the spring security library to your project. After adding it, you create a class to configure Spring security.
A function in the class config for Spring security.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/api/statistical/**",
"/static/**",
"/webjars/**",
"/img/**",
"/css/**",
"/js/**",
"/api/diary/**")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/users/**") //, "/api/polls/**"
.permitAll()
.anyRequest()
.authenticated();
I'm using Identity Server 3 with WS-Federation plugin on top and Azure AD SAML2 Provider as an External Provider in Identity Server 3.
No Problems when I perform Single Sign On between IDSRV3, Client, Relying Party and AZURE AD.
The problem is when I perform Single Sign Out from Azure with authenticated RP (Ws Federation Client).
This is what I'm getting in Firefox:
Load denied by X-Frame-Options: https://localhost:5000/core/wsfed/signout does not permit cross-origin framing.
In Chrome:
Refused to display 'https://localhost:5000/core/wsfed/signout' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
As I see in the WsFederation plugin the "/wsfed/signout" route does not disable the XFO
[Route("signout")]
[HttpGet]
public async Task<IHttpActionResult> SignOutCallback()
{
Logger.Info("WS-Federation signout callback");
var urls = await _cookies.GetValuesAndDeleteCookieAsync(WsFederationPluginOptions.CookieName);
return new SignOutResult(urls);
}
But do on the "/wsfed" GET route:
[Route("")]
[SecurityHeaders(EnableCsp = false, EnableXfo = false)]
public async Task<IHttpActionResult> Get()
{
...
if (signout != null)
{
Logger.Info("WsFederation signout request");
return await ProcessSignOutAsync(signout);
}
Where is the mistake here? If I remove the X-Frame-Options headers in IIS directly it works fine but following the docs does not.
Any help is appreciated.
I need to add single-sign-on using Windows Authentication to my intranet Angular web application (hosted on IIS) which uses a JWT Bearer token for authentication. The controllers are secured using the [Authorize] attribute and JWT Bearer token authentication is working. All of the controllers are exposed under the api/ route.
The idea is to publish a new SsoController under the sso/ route, which should be secured with Windows Authentication and that exposes a WindowsLogin action that returns a valid bearer token for the application.
Back when I was using ASP.net Web Forms it was quite easy, you only had to enable Windows Authentication in the web.config/system.webServer section, disable it application-wide in the system.web section and then enable it again under a <location path="sso"> tag. This way ASP.net generated the NTLM/Negotiate challenges only for requests under the sso route.
I got it almost working - the SsoController gets the Windows user name and creates the JWT token just fine, but the pipeline is still generating the WWW-Authenticate: NTLM and WWW-Authenticate: Negotiate headers for all HTTP 401 responses, not just for the ones under the sso route.
How can I tell the pipeline that I want only Anonymous or Bearer auth for all of the api/ requests?
Thanks in advance for your help.
Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseIISIntegration();
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Set up data directory
services.AddDbContext<AuthContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AuthContext")));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "AngularWebApp.Web",
ValidAudience = "AngularWebApp.Web.Client",
IssuerSigningKey = _signingKey,
ClockSkew = TimeSpan.Zero //the default for this setting is 5 minutes
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseWhen(context => context.Request.Path.StartsWithSegments("/sso"),
builder => builder.UseMiddleware<WindowsAuthMiddleware>());
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");
}
});
}
WindowsAuthMiddleware.cs
public class WindowsAuthMiddleware
{
private readonly RequestDelegate next;
public WindowsAuthMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync(IISDefaults.AuthenticationScheme);
return;
}
await next(context);
}
}
web.config
<system.webServer>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true"/>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
So, I spent the last few days investigating this problem and I got a working - if a bit hacky - solution.
It turns out that the main problem is that IIS will handle the Windows Authentication negotiation for all 401 responses sent by the application. It's something that's done at a lower level as soon as you enable Windows Authentication in IIS (or in the system.webServer section), and I haven't been able to find a way to bypass this behaviour. I actually did a test with a classic Web Form app and it works the same - the reason I never noticed this is that classic Forms Authentication rarely generates 401 responses, rather it uses redirects (30x) to take the user to the login page.
This gave me an idea: I could add another middleware to the pipeline that rewrites 401 responses generated by the authorization infrastructure to another, rarely used HTTP code, and detect that in my client Angular app to make it behave as a 401 (by refreshing an access token, or denying router navigation, etc). I used HTTP error 418 "I'm a teapot" since it's an existing but unused code. Here is the code:
ReplaceHttp401StatusCodeMiddleware.cs
public class ReplaceHttp401StatusCodeMiddleware
{
private readonly RequestDelegate next;
public ReplaceHttp401StatusCodeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
if (context.Response.StatusCode == 401)
{
// Replace all 401 responses, except the ones under the /sso paths
// which will let IIS trigger the Windows Authentication mechanisms
if (!context.Request.Path.StartsWithSegments("/sso"))
{
context.Response.StatusCode = 418;
context.Response.Headers["X-Original-HTTP-Status-Code"] = "401";
}
}
}
}
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
// Enable the SSO login using Windows Authentication
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/sso"),
builder => builder.UseMiddleware<WindowsAuthMiddleware>());
app.UseMiddleware<ReplaceHttp401StatusCodeMiddleware>();
...
}
The middleware also injects the original status code in the response for further reference.
I also applied to my code the suggestion from Mickaël Derriey to use Authorization policies because it makes the controllers cleaner, but it's not necessary for the solution to work.
Welcome to StackOverflow! That's an interesting quesiton you have here.
First, let me state that I didn't test any of the content in this answer.
Using authorization policies to drive sources of authentication
I like the idea behind the WindowsAuthMiddleware you created, and how it's conditionally inserted in the pipeline if the URL starts with /sso.
MVC integrated with the authorization system and provides the same capabilities with authorization policies. The result is the same, and prevents you from having to write low-level code.
You can define authorization policies in the ConfigureServices method. In your case, if I'm not mistaken, there are two policies:
all requests to /sso should be authenticated with Windows authenticated; and
all other requests should be authenticated with JWTs
services.AddAuthorization(options =>
{
options.AddPolicy("Windows", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(IISDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
});
You can then reference those policies by name in the [Authorize] attributes used to decorate your controllers and/or actions.
[Authorize("Windows")]
public class SsoController : Controller
{
// Actions
}
[Authorize("JWT")]
public class ApiController : Controller
{
// Actions
}
Doing so means that the Windows authentication handler will not run against /api requests, hence the responses should not contain the WWW-Authenticate: NTLM and WWW-Authenticate: Negotiate headers.
Removing automatic authentication of all requests
When you pass an authentication scheme as an argument of AddAuthentication, this means the authentication middleware will try to authenticate every request against that scheme.
This is useful when you have one authentication scheme, but in this case, you could think about removing it, as even for requests to /sso, the JWT handler will analyze the request for a token.
Two calls to AddAuthentication
You should only have one call to AddAuthentication:
the first one sets the IIS authentication scheme as a default so the handler should run on every request;
the second call overwrites that setting and set the JWT scheme as the default one
Let me know how you go!
The upgrade request for opening a websocket connection is a standard HTTP request. On the server side, I can authenticate the request like any other. In my case, I would like to use Bearer authentication. Unfortunately, there is no way to specify headers when opening a websocket connection in the browser, which would lead me to believe that it's impossible to use bearer authentication to authenticate a web socket upgrade request. So -- Am I missing something, or is it really impossible? If it is impossible, is this by design, or is this a blatant oversight in the browser implementation of the websocket API?
The API allows you to set exactly one header, namely Sec-WebSocket-Protocol, i.e. the application specific subprotocol. You could use this header for passing the bearer token. For example:
new WebSocket("ws://www.example.com/socketserver", ["access_token", "3gn11Ft0Me8lkqqW2/5uFQ="]);
The server is expected to accept one of the protocols, so for the example above, you can just validate the token and respond with header Sec-WebSocket-Protocol=access_token.
You are right, it is impossible for now to use Authentication header, because of the design of Javascript WebSocket API.
More information can be found in this thread:
HTTP headers in Websockets client API
However, Bearer authentication type allows a request parameter named "access_token": http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#query-param
This method is compatible with websocket connection.
Example for basic authentication using token servlet http request header before websocket connection:
****ws://localhost:8081/remoteservice/id?access_token=tokenValue****
verify your token return true if valid else return false
endpoint configuration:
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer{
#Autowired
RemoteServiceHandler rsHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
registry.addHandler(rsHandler, "/remoteservice/{vin}").setAllowedOrigins("*").addInterceptors(new HttpHandshakeInterceptor());
}
}
validate the token before established websocket connectin:
public class HttpHandshakeInterceptor implements HandshakeInterceptor{
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception
{
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String token = servletRequest.getServletRequest().getHeader("access_token");
try {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
if (claims!=null) {
return true;
}
} catch (Exception e) {
return false;
}
return false;
}
skip the http security endpoint
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().anyRequest();
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
add the request header in js file as you like
var request = URLRequest(url: URL(string: "ws://localhost:8081/remoteservice")!)
request.timeoutInterval = 5 // Sets the timeout for the connection
request.setValue("someother protocols", forHTTPHeaderField: "Sec-WebSocket-Protocol")
request.setValue("14", forHTTPHeaderField: "Sec-WebSocket-Version")
request.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol")
request.setValue("Everything is Awesome!", forHTTPHeaderField: "My-Awesome-Header")
let socket = WebSocket(request: request)
I'm trying to do custom OAuth2 authorization server that will support Resource Owner Password Credentials flow. The authorization server is an WebAPI application hosted in IIS7.5.
I have configured startup class where I register custom OAuthServerProvider (AtcAuthorizationServerProvider).
[assembly: OwinStartup(typeof(ATC.WebApi.AuthorizationServer.Startup))]
namespace ATC.WebApi.AuthorizationServer
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new AtcAuthorizationServerProvider(),
RefreshTokenProvider = new AtcRefreshTokenProvider(),
AuthenticationMode = AuthenticationMode.Passive
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions(){});
}
}
}
In my custom provider class, I override ValidateClientAuthentication() function where I accept both client credentials receiving ways (in Body and in Authorization header).
public class AtcAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
// get client credentials from header or from body
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
//rest of code
Everything works fine when I send client_id and client_secret in body.
POST /ATC.WebApi.AuthorizationServer/token HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
grant_type=password&password=123456&username=myUser&client_id=myClient&client_secret=123%40abc
I get access token successfully.
{
"access_token": "3Fk_Ps10i45uL0zeCzIpvEh2WHKE8iJVNtKJ2XGWcQWXsT9jllKf...",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "4c1097d17dd14df5ac1c5842e089a88e",
"as:client_id": "myClient"
}
However, if I use DotNetOpenAuth.OAuth2.WebServerClient which passes client_id and client_secret in Authorization header I will recieve 401.1 - Unauthorized HTTP response. I have found out that the ValidateClientAuthentication() is not fired.
Request than looks like this:
POST /ATC.WebApi.AuthorizationServer/token HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic C16b34lUjEyM0BhYmM=
Cache-Control: no-cache
grant_type=password&password=123456&username=myUser
The question is how to persuade probably the OWIN middle-ware firing my custom Provider in this case?
Well, I finally found out where is the trouble. There was Basic authentication allowed in my IIS, so IIS got the request and tried to Authenticate User which failed and IIS returned 401 Unauthorized immediately. So my OWIN middleware even did not receive the request to processing.