Springboot + JWT +OAuth2 + AngularJS Stateless session - angularjs

I am trying various Java Spring based security implementations as follows
1. JWT Authentication
User access /
Springboot identifies as protected resource and redirects user to /login
User enters credentials and browsers does a POST to /authenticate
Server validates the credentials and generates JWT token. Set into response header and redirects to /
Browser loads /. AngularJS recognizes JWT token in the response header and stores the same in the localStorage
All subsequent calls will have the Bearer token in header (injected through httpInterceptor)
Note: Stateless Session
2. OAuth2 authentication
User access /
Springboot identifies as protected resource and redirects user to /login
/login is intercepted by Spring security. Redirects to Oauth2 authorization server with a generated state and redirect URL back to application
User enters credentials
Oauth server redirects back to application URL "/login?code=xxx&state=yyy"
/login is intercepted by Spring security. Recognizes the code and state, generates Cookie and sets in response header. Redirects to /
Browser loads /. Browser recognizes cookie in the response header and stores the same.
If a call is made to /user, the Principal object is populated with the JWT which I am able to extract as follows
#RequestMapping(value= {"/user")
public ResponseEntity<Map<String, String>> user(Principal principal) throws Exception {
OAuth2Authentication obj = (OAuth2Authentication) principal;
authentication = obj.getUserAuthentication();
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) obj.getDetails();
String jwt = oAuth2AuthenticationDetails.getTokenValue();
All subsequent calls will have the Cookie in the Request
Note: A Stateful Session is created in server side to store the session details. This required to decrypt the cookie and identify the user
Now I want to implement security using Oauth2+JWT but stateless at same time as follows
3. OAuth2 + JWT + Stateless
User access /
Springboot identifies as protected resource and redirects user to /login
/login is interecepted by Spring security. Redirects to Oauth2 authorization server with a generated state and redirect URL back to application
User enters credentials
Oauth server redirects back to application URL "/login?code=xxx&state=yyy"
/login is intercepted by Spring security. Recognizes the code and state, extract JWT token by invoking
OAuth2AuthenticationDetails.getTokenValue() and set in response
header. Redirect to /
Browser loads /. AngularJS recognizes JWT token in the response header and stores the same in the localStorage
All subsequent calls will have the Bearer token in header (injected through httpInterceptor)
Question
I am trying to figure out how to implement the highlighted step above

Just an idea/direction, if I got you right:
You can create a GenericFilterBean and add that to the HttpSecurity filter chain.
When using JWT, there should be something similar (a filter, which extracts the bearer-token from the header) and then populates an Authentication object for Spring Security.
So the new filter could grab the token from the request and set the response accordingly.
You could also handle that in an unprotected (!) callback endpoint like login/callback?..., which than sets the cookie for you.
In our application, the server (spring boot) is totally stateless and does not have any oauth nor stateful stuff. Obviously it never redirects anything or has any other views/endpoints than / for AngularJS (and some REST-APIs under /api/...). Thus, the OAuth-flow is totally handled by AngularJS, which in turn checks the callback from the oauth-server and locally sets the JWT-Token (like in your first approach). In first versions we also tried to mix up redirects with stateless JWT and stateful sessions etc., but this led to very strange behavior with the logins - the state (logged in or not) was not always clear and in some cases redirects were wrong etc.

This might help you implement your desired solution.
The author proposes that once a user successfully authenticates with Oauth2 providers (Google etc), you send a short-lived token as a URL param to your frontend application and use this short-lived token to exchange it for a longer-lived token.

Related

Azure Active Directory SSO (SAML protocol) when frontend is decoupled from backend. How will frontend be notified that validation is completed?

So I would like to add SSO using Azure AD.
My stack consists of a React app as frontend and a NestJS API as backend(decoupled). The scenario looks like the following
User clicks login button
I create a new window (popup) which leads the user to the Azure AD login page (step 3 in the diagram)
After the user logs in to Azure AD successfully Azure AD will POST the SAML response to a redirect URL I have provided them (let's say http://myapp.com/saml) (step 5 in the diagram) (This redirect URL, to my understanding, has to be an endpoint in the NestJS api since React frontend can't handle post request.)
NestJS will handle the post request get the info it needs, validates etc etc and then NestJS has to return a Token to the frontend somehow in order for the frontend to store that token in a cookie and be able use it in subsequent requests to the NestJS api so that NestJS will be able to know that this user is logged in. (Step 6)
My issue with this approach is that I don't know how will the client get the token when the the validation is completed from NestJS. If this was a coupled application this would not be an issue since the backend would handle the post request set a cookie and redirect the user. But in this case NestJS can't redirect the user since react handles the routing.
What is the correct approach to handle this?
I thought maybe this could work by using websockets...so that when NestJS handles the post request it can send a message to the user which message will contain the token and then the frontend can add it to a cookie and redirect the user to a protected page.
(1) Does the frontend really need to store a token to send to the backend on subsequent requests? What if the backend set a cookie at Step 7 in your diagram? Then the cookie would be sent to the backend on subsequent requests. The cookie would be scoped to the backend’s domain name and path. Keeping tokens away from the frontend has the advantage that you can keep them safe from being accessed via a Cross-Site Scripting attack on your frontend, if your backend’s cookie has the HttpOnly attribute set.
(2) If you still need to communicate info from the backend to the frontend in Step 7, then send an HTTP 301 response with the Location header set to your frontend’s URL with the info you want to communicate included in the query or hash portion of the URL. For example, after validating in Step 6, in Step 7 your backend’s HTTP 301 response could have the Location header set to https://my-frontend-domain.com?user=bob or set to https://my-frontend-domain.com#user=bob. With ?user=bob, user=bob would get sent over the network again when the browser requests https://my-frontend-domain.com/?user=bob, whereas with #user=bob it would not. Then the frontend’s JavaScript can read user=bob from the URL.

How to safely persist logged-in state of user in React website?

I have a website that runs on example.com. The website makes AJAX calls to my backend API which sits at api.example.com.
I employ a double-submit verification strategy for my authentication.
The backend has protected endpoints which check the JWT token with each request. The JWT token is stored in a httpOnly cookie. It also contains the CSRF token.
Alongside the JWT cookie I also send a CSRF cookie which is not httpOnly.
Each request that the client makes must contain the value of this cookie in a special header.
So far so good, but, I want to make sure that the client does everything in its power to prevent users from making pointless unauthenticated requests. So in my React app I have declared a few private routes which check if the user is logged in and if they are not, the user is redirected to the login page. The logged-in state is kept in the Redux store.
The issue is that on a full refresh the Redux store is reset. So as I see it, my options are:
Option 1) Check the existence of a CSRF cookie
The way I check if a user is authenticated is by checking if they have the CSRF cookie; I also do this during the store initialisation. If they do have the cookie, they are allowed to navigate to the protected page. All subsequent requests on this page are still verified on the backend for a JWT and CSRF token.
Option 2) Check against the backend each time
I can create a simple endpoint on the backend that is used to check if the user is logged in. So instead of just checking the cookie, the client can submit a request to this endpoint and verify that the token in the cookie is still valid.
Option 3) Persist the Redux store
I could implement a persisted store (there are some libraries that do this out there) so that the initial problem is automatically resolved, i.e.: the logged-in state is preserved upon full refresh. However, this yields a bunch of issues with caching and token expiration and it may not be worth the effort.

MVC with Angular - ValidateAntiForgeryToken fails after Azure Login redirect with Adal

I have an MVC site with an embedded angular client and I've recently implemented an anti forgery XSRF token as a security measure.
I have set it up in Startup.cs as follows:
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
app.Use(next => context =>
{
if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// We can send the request token as a JavaScript-readable cookie, and Angular will use it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
And I've implemented it within my angular front-end like so:
{ provide: XSRFStrategy, useFactory: xsrfFactory}
export function xsrfFactory(): CookieXSRFStrategy {
return new CookieXSRFStrategy('XSRF-TOKEN', 'X-XSRF-TOKEN');
}
And protecting my controllers like:
[Authorize] //Validation of AzureAD Bearer Token.
[ValidateAntiForgeryToken]
public class UserController : Controller
It is intended that the X-XSRF-TOKEN header be validated with any call to my API, and this works successfully for all calls in the original session. However, my app uses Adal to log the user in, and after the redirect from a successful login, this validation step fails and I receive a 400 from my API for any subsequent calls.
The original X-XSRF-TOKEN header is still sent with all outgoing requests from my angular client after the login so I suspect it must be that my server side no longer has the token to validate against, or my server has generated a new one and my client doesn't retrieve it. But for whatever reason it breaks down and it's very hard to debug without creating some custom filter so I can see what's going on inside it.
Is there a way to reset this token after a client side redirect so that both my server and client share common knowledge of it again? Or should I be generating the token in my Index.html for example?
EDIT
Edited controller decoration above for missing [Authorize] attribute.
So my controller is protected by a step validating the AzureAD Bearer token as well as the Anti-Forgery validation. Removing the AzureAD Validation as a test did not resolve the issue, oddly.
Error on failing API calls displays in output after Adal login as:
The provided anti-forgery token was meant for a different claims-based user than the current user.
Based on my understanding, you were protecting the controller using token. For this issue is expected, you can refer the progress of validate the anti-XSRF tokens from below(refer this link):
To validate the incoming anti-XSRF tokens, the developer includes a ValidateAntiForgeryToken attribute on her MVC action or controller, or she calls #AntiForgery.Validate() from her Razor page. The runtime will perform the following steps:
The incoming session token and field token are read and the anti-XSRF token extracted from each. The anti-XSRF tokens must be identical per step (2) in the generation routine.
If the current user is authenticated, her username is compared with the username stored in the field token. The usernames must match.
If an IAntiForgeryAdditionalDataProvider is configured, the runtime calls its ValidateAdditionalData method. The method must return the Boolean value true.
Since you were developing the SPA application with back-end web API, when the request to the web API, it will always issue the anti-XSRF token with no identity. And when you send the request to the back-end with anti-XSRF and Azure AD token, this time the web API already authenticate the request via the Azure AD token. And it will always return false when checking anti-XSRF token to match the identity information.
In this scenario, if the back-end only using the bear token authentication and store the token with session storage, there is no need to enable XSRF prevention since others is not able to steal the token and forge the request.
If your back-end also support the cookie authentication or basic auth, NTLM etc, you can disable the identity checking by adding the following to your Application_Start method: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.(refer this link)
More detail about XSRF/CSRF abouth oauth and web API, you can refer the threads below:
How does ValidateAntiForgeryToken fit with Web APIs that can be accessed via web or native app?
AntiForgeryToken does not work well with OAuth via WebAPI
Try replacing [ValidateAntiForgeryToken] with [AutoValidateAntiforgeryToken]
https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs

JWT not sent when loading url directly from browser address bar

I have a MEAN stack application that uses JWT for authentication. I use Satellizer (an Angular module) to implement the JWT authentication flow. It sends the Authorization header with the bearer token whenever requests are made through the app.
My problem is when a request to a restricted url, lets say /dashboard is made through the address bar, the browser does not send an authorization header, thus blocking the request.
So what should I do to make the browser send the authorization header when making requests to restricted urls?
If the user directly enters an address in the bar I'm afraid you can not do anything except redirect to the error form. The browser will not send specific headers
But if you are building the links that the user clicks, then you can add the JWT token in the URL link itself /dashboard?jwt=. In the server you will have to take into account this case of authentication
Be careful in this case, the browser could cache the URL and write it in some log. If the JWT is signed and not encrypted it could leak sensitive information if an attacker has read-access to the log files. Also it could be possible a session hijacking attack.

Handling Page Reloads With OAuth Access Code In URI

I've run into an issue when using OAuth 2 authorization codes in an web app's URL, such as is returned by Google's OAuth method (https://developers.google.com/accounts/docs/OAuth2Login).
I've been using the google redirect method; where you redirect the user to a Google URL, passing in client_id and redirect_uri. The user authenticates and the authorization code is passed to the redirect_uri as a
The issue is that the access code stays in the page URL, so if the user bookmarks or posts the URL, they are sending an invalid Authorization Code.
Eg:
http://myapp.com/?code=kACASDSDdAS81J5B8M_owCyUNgV46XdZaqBBMh4T8OJFEKPRrgN7gtiFOcMW5Fv3gk
What is the best way to handle this case? Ideally, I would like to send the authorization code in a POST body as it isn't visible to the player?
I've spent a bit of time looking at Google App Engine (the platform I'm using) to redirect the user, but can't seem to send a POST body in a redirect.
After the user is directed to your app with the authorization code in the URL query parameter, you should:
1) Exchange the authorization code for an access token by making a HTTPs POST to Google's OAuth 2.0 token endpoint and save that access token as appropriate (datastore, memcache, etc)
2) Redirect the user to a URL without the ?code. You can't send a POST body in a redirect (HTTP doesn't allow it), but that shouldn't be necessary if you store the access token server-side for making API calls.
If you must make the token accessible client-side, you can:
a) Send it back as a cookie along with the redirect (which exposes it to the client, though you could encrypt it) OR
b) Generate a HTML form, with JavaScript for auto-submitting it instead of doing the redirect. Kind of ugly, but common.

Resources