Spring Social The OAuth2 'state' parameter is missing or doesn't match? [duplicate] - angularjs

I am trying to use Spring Social on my application and I noticed while debugging that the original 'OAuth2' state parameter is always null on my app.
See Spring Social source code for org.springframework.social.connect.web.ConnectSupport below:
private void verifyStateParameter(NativeWebRequest request) {
String state = request.getParameter("state");
String originalState = extractCachedOAuth2State(request);//Always null...
if (state == null || !state.equals(originalState)) {
throw new IllegalStateException("The OAuth2 'state' parameter is missing or doesn't match.");
}
}
private String extractCachedOAuth2State(WebRequest request) {
String state = (String) sessionStrategy.getAttribute(request, OAUTH2_STATE_ATTRIBUTE);
sessionStrategy.removeAttribute(request, OAUTH2_STATE_ATTRIBUTE);
return state;
}
Can anyone please help?
edit: I do see the state parameter being passed back by facebook:
Request URL:https://www.facebook.com/v2.5/dialog/oauth?client_id=414113641982912&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fconnect%2Ffacebook&scope=public_profile&state=0b7a97b5-b8d1-4f97-9b60-e3242c9c7eb9
Request Method:GET
Status Code:302
Remote Address:179.60.192.36:443
edit 2: By the way, the exception I get is the following:
Exception while handling OAuth2 callback (The OAuth2 'state' parameter is missing or doesn't match.). Redirecting to facebook connection status page.

It turned out that the issue was caused by the fact that I was relying on headers - as opposed to cookies - to manage the session.
By commenting out the following spring session configuration bean:
#Bean
public HttpSessionStrategy sessionStrategy(){
return new HeaderHttpSessionStrategy();
}
The oauth2 state parameter issue was sorted.
P.S. Now I have got to find a way to get Spring Social to work with my current configuration of Spring Session...
Edit: I managed to keep the HeaderHttpSessionStrategy (on the spring session side) and get it to work by implementing my own SessionStrategy (on the spring social side) as follows:
public class CustomSessionStrategy implements SessionStrategy {
public void setAttribute(RequestAttributes request, String name, Object value) {
request.setAttribute(name, value, RequestAttributes.SCOPE_SESSION);
}
public Object getAttribute(RequestAttributes request, String name) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
return servletWebRequest.getParameter(name);
}
public void removeAttribute(RequestAttributes request, String name) {
request.removeAttribute(name, RequestAttributes.SCOPE_SESSION);
}
}

Try this work around and see if that works for you:
To my surprise I opened application in a 'incognito' browser and everything worked. Just like that. I think before something got cached and was causing the issue.
I ran into this issue today, My application was working perfectly fine. I just took a break for few hours and when I ran it again it started complaining about 'The OAuth2 'state' parameter is missing or doesn't match.'
The state param is first put into the session then the request goes out to facebook and the request comes back with the same state param but when spring is looking for session object to get the state param, it is not finding the session. I think it is not finding the session because when the request comes back it thinks that it is a different client (or host), even though the old HttpSession object still exists. The container maintains a HttpSession per client.

What you're getting from Facebook is not a request attribute , it's a request parameter.
You should get it by something like:
request.getParameter("state")

Related

Spring boot rest controller POST request always create new sessionid using #SessionScope?

I have searched a lot and found no suitable answers.
I have 2 post controller method as
#PostMapping("/saveStudentInfo")
public String saveStudentInfo(#RequestBody Students stud, HttpServletRequest request) {
students.setId(stud.getId());
students.setStudentName(stud.getStudentName());
students.setSchoolInfo(stud.getSchoolInfo());
System.out.println("Hello A= "+request.getSession(false).getId());
return "Saved Sucessfully";
}
#PostMapping("/saveSubjectInfo")
public String saveSubjectInfo(#RequestBody Subjects sub, HttpServletRequest request) {
subject.setSubjectName(sub.getSubjectName());
subject.setSubjectTeacher(sub.getSubjectTeacher());
System.out.println("Hello B= "+request.getSession(false).getId());
return "Saved Sucessfully Subject";
}
Now from postman these calls, works successfully, as same JSessionId is generated
==>Problem
When called from react app via browser on 2nd
saveSubjectInfo request I get different JSessionId, as in New-Session was Created
And I have annotated both Students and Subjects as #SessionScoped.
==>Requirement
Need to maintain session, and for that JSessionId needed to be same.
By default browsers support the GET method when API is hit directly from the browser URL bar. Also the POST and PUT method are usually supposed to make request along with some data to the serving method as the body so that it is not viewed in the browser request. For POST or PUT Postman app can be used.

Session Id is changing every request in ASP.NET core 2.1 web api

I am working on asp.net core web application. I am not much aware of session state. When i login the application i have got the session id and I have pushed some user data's into session using HttpContext.Session. That values are under current sessionid. And then i sent that sessionid to client(browser). where i having using angularjs.
Whenever i sent the request after login. i have sending the sessionid along with request in request header. But after request reached my backend(WebApi) where i have seen another sessionid in HttpContext.Session. but i have my sessionid which that having those session values. But using current HttpContext.Session's sessionid couldn't get those session values.
Whether i have to get those session values by my sessionid which was came along request header or have to change HttpContext.Session's sessionid or Won't allow to change HttpContext.Session's sessionid for every request. Everything i have mentioned is have tried but couldn't do anything.What else is there? Please someone help me!. I could seen lot of answers about it in online but everything is for .Net Framework not for ASP.Net core 2.1.
Here is my code,
I have set session values in my user controller. and after login i will fill data in my quote creation page and give submit. Meanwhile i have to get my session values(which i stored in user controller login method) in my quote controller create method. So current request sessionid is changed in Httpcontext(even i use IHttpContextAccessor).so couldn't get my session values
public class UserController : Controller {
public string Login(User objUser)
{...
HttpContext.Session.SetString("CompanyId", "1");
...}
}
public class QuoteController : Controller {
public string CreateQuote([FromBody]Quote objQuote)
{...
var companyId = HttpContext.Session.GetString("CompanyId");
...}
}
This should help. You can access season
by HttpContext.
Check this example how work with seasson string.
using Microsoft.AspNetCore.Http;
public class HomeController : Controller {
public IActionResult Index()
{
HttpContext.Session.SetString("Test", "Testing Session.");
return View();
}
public IActionResult About()
{
ViewBag.Message = HttpContext.Session.GetString("Test");
return View();
}
}

Cannot get Username / given_name when using angular-oauth2-oidc and Identity Server 4

I am following the Implicit Workflow example from the angular-oauth2-oidc documentation.
Everything works well in my Angular app, and I can login (during which I am redirected to Identity Server), get my token and use this token to access my Web Api.
However, I have noticed that the "given_name" claim is null, and therefore, the username is not displayed on the login page. Specifically, the following method from the sample code appears to return null:
public get name() {
let claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims.given_name;
}
I thought perhaps this was a problem with permissions, but my scope is set to:
scope: 'openid profile email api1',
Any idea what I need to change to get this "given_name" claim?
For those who encountered the same issue. You can fix it by adding this line AlwaysIncludeuserClaimsInIdToken=true in the client settings on identity provider side.
OauthService.getIdentityClaims() is a Promise and holds UserInfo you can extract the name field with braces, so your function should be:
public get name() {
let claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims['name'];
}
The answer marked as "Best answer" is not correct. Get the user claims in the 'idtoken' will cause that the 'idtoken' be very big and then you may exceed the size limit.
The correct implementation is to use the 'UserInfo' Endpoint and then use the method 'loadUserProfile':
Example:
getUserClaims() {
const user = this.oauthService.loadUserProfile();
console.log(user, user);
}
I had the same issue, in my case with an error displayed on the browser console, saying that Request was blocked by Security Policy.
even having the AllowAnyOrigin() method called in startup, I lacked to get the header allowed. So when in my angular aap i call the loadUserProfile method via the
token_received event, it sends some headers that were not allowed.
Finaly this fix my issue:
app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader());
Don't forget calling that before usemvc

Building realtime app using Laravel and Latchet websocket

I'm building a closed app (users need to authenticate in order to use it). I'm having trouble in identifying the currently authenticated user from my Latchet session. Since apache does not support long-lived connections, I host Latchet on a separate server instance. This means that my users receive two session_id's. One for each connection. I want to be able to identify the current user for both connections.
My client code is a SPA based on AngularJS. For client WS, I'm using the Autobahn.ws WAMP v1 implementation. The ab framework specifies methods for authentication: http://autobahn.ws/js/reference_wampv1.html#session-authentication, but how exactly do I go about doing this?
Do I save the username and password on the client and retransmit these once login is performed (which by the way is separate from the rest of my SPA)? If so, won't this be a security concearn?
And what will receive the auth request server side? I cannot find any examples of this...
Please help?
P.S. I do not have reputation enough to create the tag "Latchet", so I'm using Ratchet (which Latchet is built on) instead.
Create an angularjs service called AuthenticationService, inject where needed and call it with:
AuthenticationService.check('login_name', 'password');
This code exists in a file called authentication.js. It assumes that autobahn is already included. I did have to edit this code heavily removing all the extra crap I had in it,it may have a syntax error or two, but the idea is there.
angular.module(
'top.authentication',
['top']
)
.factory('AuthenticationService', [ '$rootScope', function($rootScope) {
return {
check: function(aname, apwd) {
console.log("here in the check function");
$rootScope.loginInfo = { channel: aname, secret: apwd };
var wsuri = 'wss://' + '192.168.1.11' + ':9000/';
$rootScope.loginInfo.wsuri = wsuri;
ab.connect(wsuri,
function(session) {
$rootScope.loginInfo.session = session;
console.log("connected to " + wsuri);
onConnect(session);
},
function(code,reason) {
$rootScope.loginInfo.session = null;
if ( code == ab.CONNECTION_UNSUPPORTED) {
console.log(reason);
} else {
console.log('failed');
$rootScope.isLoggedIn = 'false';
}
}
);
function onConnect(sess) {
console.log('onConnect');
var wi = $rootScope.loginInfo;
sess.authreq(wi.channel).then(
function(challenge) {
console.log("onConnect().then()");
var secret = ab.deriveKey(wi.secret,JSON.parse(challenge).authextra);
var signature = sess.authsign(challenge, secret);
sess.auth(signature).then(onAuth, ab.log);
},ab.log
);
}
function onAuth(permission) {
$rootScope.isLoggedIn = 'true';
console.log("authentication complete");
// do whatever you need when you are logged in..
}
}
};
}])
then you need code (as you point out) on the server side. I assume your server side web socket is php coding. I can't help with that, haven't coded in php for over a year. In my case, I use python, I include the autobahn gear, then subclass WampCraServerProtocol, and replace a few of the methods (onSessionOpen, getAuthPermissions, getAuthSecret, onAuthenticated and onClose) As you can envision, these are the 'other side' of the angular code knocking at the door. I don't think autobahn supports php, so, you will have to program the server side of the authentication yourself.
Anyway, my backend works much more like what #oberstat describes. I establish authentication via old school https, create a session cookie, then do an ajax requesting a 'ticket' (which is a temporary name/password which i associate with the web authenticated session). It is a one use name/password and must be used in a few seconds or it disappears. The point being I don't have to keep the user's credentials around, i already have the cookie/session which i can create tickets that can be used. this has a neat side affect as well, my ajax session becomes related to my web socket session, a query on either is attributed to the same session in the backend.
-g
I can give you a couple of hints regarding WAMP-CRA, which is the authentication mechnism this is referring:
WAMP-CRA does not send passwords over the wire. It works by a challenge-response scheme. The client and server have a shared secret. To authenticate a client, the server will send a challenge (something random) that the client needs to sign - using the secret. And only the signature is sent back. The client might store the secret in browser local storage. It's never sent.
In a variant of above, the signing of the challenge the server sends is not directly signed within the client, but the client might let the signature be created from an Ajax request. This is useful when the client was authenticated using other means already (e.g. classical cookie based), and the signing can then be done in the classical web app that was authenticating.
Ok, Greg was kind enough to provide a full example of the client implementation on this, so I wont do anything more on that. It works with just a few tweaks and modifications to almost any use-case I can think of. I will mark his answer as the correct one. But his input only covered the theory of the backend implementation, so I will try to fill in the blanks here for postparity.
I have to point out though, that the solution here is not complete as it does not give me a shared session between my SPA/REST connection and my WS connection.
I discovered that the authentication request transmitted by autobahn is in fact a variant of RPC and for some reason has hardcoded topic names curiously resembling regular url's:
- 'http://api.wamp.ws/procedure#authreq' - for auth requests
- 'http://api.wamp.ws/procedure#auth' - for signed auth client responses
I needed to create two more routes in my Laravel routes.php
// WS CRA routes
Latchet::topic('http://api.wamp.ws/procedure#authreq', 'app\\socket\\AuthReqController');
Latchet::topic('http://api.wamp.ws/procedure#auth', 'app\\socket\\AuthReqController');
Now a Latchet controller has 4 methods: subscribe, publish, call and unsubscribe. Since both the authreq and the auth calls made by autobahn are RPC calls, they are handled by the call method on the controller.
The solution first proposed by oberstet and then backed up by Greg, describes a temporary auth key and secret being generated upon request and held temporarily just long enough to be validated by the WS CRA procedure. I've therefore created a REST endpoint which generates a persisted key value pair. The endpoint is not included here, as I am sure that this is trivial.
class AuthReqController extends BaseTopic {
public function subscribe ($connection, $topic) { }
public function publish ($connection, $topic, $message, array $exclude, array $eligible) { }
public function unsubscribe ($connection, $topic) { }
public function call ($connection, $id, $topic, array $params) {
switch ($topic) {
case 'http://api.wamp.ws/procedure#authreq':
return $this->getAuthenticationRequest($connection, $id, $topic, $params);
case 'http://api.wamp.ws/procedure#auth':
return $this->processAuthSignature($connection, $id, $topic, $params);
}
}
/**
* Process the authentication request
*/
private function getAuthenticationRequest ($connection, $id, $topic, $params) {
$auth_key = $params[0]; // A generated temporary auth key
$tmpUser = $this->getTempUser($auth_key); // Get the key value pair as persisted from the temporary store.
if ($tmpUser) {
$info = [
'authkey' => $tmpUser->username,
'secret' => $tmpUser->secret,
'timestamp' => time()
];
$connection->callResult($id, $info);
} else {
$connection->callError($id, $topic, array('User not found'));
}
return true;
}
/**
* Process the final step in the authentication
*/
private function processAuthSignature ($connection, $id, $topic, $params) {
// This should do something smart to validate this response.
// The session should be ours right now. So store the Auth::user()
$connection->user = Auth::user(); // A null object is stored.
$connection->callResult($id, array('msg' => 'connected'));
}
private function getTempUser($auth_key) {
return TempAuth::findOrFail($auth_key);
}
}
Now somewhere in here I've gone wrong. Cause if I were supposed to inherit the ajax session my app holds, I would be able to call Auth::user() from any of my other WS Latchet based controllers and automatically be presented with the currently logged in user. But this is not the case. So if somebody see what I'm doing wrong, give me a shout. Please!
Since I'm unable to get the shared session, I'm currently cheating by transmitting the real username as a RPC call instead of performing a full CRA.

How to handle security/authentication on a DNN-based web API

I am building a REST API for a DotNetNuke 6 website, making use of DNN's MVC-based Services Framework. However, I don't have any background in authentication, so I'm not even sure where to start.
Basically, we want our clients to be able to make GET requests for their portal's data, and we want some clients (but not all) to be able to POST simple updates to their user data.
I've been trying to search for information, but the trouble is I'm not sure what I'm searching for. DNN has different logins and roles, but I'm not sure if or how they factor in. I've heard of things like oAuth but my understanding of it is at the most basic level. I don't know if it's what I need or not and if or how it applies to DNN. Can anyone point me in the right direction?
UPDATE:
Based on the answer below about tying it with a module and further research, here is what I have done:
I created a module just for this service, and I added two special permissions for it: "APIGET" and "APIPOST." I assigned these to some test roles/test accounts in DNN. I wrote a custom authorize attribute that, given the module ID, checks if the current user has the necessary permission (either through roles or directly). As far as I can tell, tab ID is irrelevant in my case.
It appears to be working both with a web browser (based on the DNN account I'm logged into) and with a php script that sends an HTTP request with an account username/password.
The authorize attribute:
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Security;
using DotNetNuke.Security.Permissions;
using System.Web;
public class MyAuthorize : DotNetNuke.Web.Services.AuthorizeAttributeBase
{
public const string AuthModuleFriendlyName = "MyAuthModule";
public const string GETPermission = "APIGET";
public const string POSTPermission = "APIPOST";
public string Permission { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
ModuleController mc = new ModuleController();
ModuleInfo mi = mc.GetModuleByDefinition(PortalController.GetCurrentPortalSettings().PortalId, AuthModuleFriendlyName);
ModulePermissionCollection permCollection = mi.ModulePermissions;
return ModulePermissionController.HasModulePermission(permCollection, Permission);
}
}
The controller:
("mytest" is the endpoint for both GET and POST)
public class MyController : DnnController
{
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Get)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.GETPermission)]
public string myget(string id = "")
{
return "You have my permission to GET";
}
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Post)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.POSTPermission)]
public string mypost(string id = "")
{
return "You have my permission to POST";
}
}
The main way that you tie a service in the DNN Services Framework into DNN permissions is to associate the permissions with a module instance. That is, you'll require users of your service to identify which module they're calling from/about (by sending ModuleId and TabId in the request [headers, query-string, cookies, form]), then you can indicate what permissions they need on that module to take a particular action on the service.
You can use the SupportedModules attribute on your service, and pass in a comma-delimited list of module names, to ensure that only your own modules are being allowed. Then, add the DnnModuleAuthorize attribute at the service or individual action level to say what permission the user needs on that module. In your instance, you can also add the AllowAnonymous attribute on the GET actions, and have one DnnModuleAuthorize on the service, for the POST methods (and anything else). Note that you cannot put the AllowAnonymous attribute on the controller; it will override authorizations put at the action, making it impossible to make actions more restrictive.
You'll also want to add the ValidateAntiForgeryToken attribute on the POST actions, to protect against CSRF attacks.
If you don't have a module that naturally associates its permissions with your service, you can create one just for that purpose, solely to expose itself as a permissions management utility.
Once you've figured out the authorization piece above, DNN will take care of authentication using your forms cookie (i.e. AJAX scenarios are taken care of automatically), or via basic or digest authentication (for non-AJAX scenarios). That said, if you're doing non-AJAX, you'll need to figure out a way to validate the anti-forgery token only when it applies.
The Services Framework in DNN is what you are after. It allows you to provide a REST API that plugs directly into DNN security.
Here are some articles to help you get started:
http://www.dotnetnuke.com/Resources/Wiki/Page/Services-Framework-WebAPI.aspx
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3327/Getting-Started-with-DotNetNuke-Services-Framework.aspx
Note, there are some difference in DNN 6 and DNN 7 when using the Services Framework:
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3514/Converting-Services-Framework-MVC-to-WebAPI.aspx
Just wanted to note that the DnnModuleAuthorize attribute takes a PermissionKey parameter for custom permissions so you can do stuff like this:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA")]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
It doesn't look like you can supply your own error message with this so you might to wrap your method body like this instead and leave off the custom permission attribute:
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
{
var errorMessage = "Could not delete data";
if (ModulePermissionController.HasModulePermission(ActiveModule.ModulePermissions,"DELETEDATA"))
{
// do stuff here
}
else
{
errorMessage = "User does not have delete permission";
}
var error = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content =
new StringContent(
errorMessage)
};
return error;
}
Just wanted to add to #Richards comment for using the [DnnModuleAuthorize(PermissionKey = "DELETEDATA")] for custom permissions.
The full attribute should be:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA", AccessLevel = SecurityAccessLevel.Edit)]
Leaving it blank does nothing as shown here: https://github.com/dnnsoftware/Dnn.Platform/blob/f4a5924c7cc8226cfe79bbc92357ec1a32165ada/DNN%20Platform/Library/Security/Permissions/PermissionProvider.cs#L810
I guess you require a plugin that allows you to construct GET and POST APIs. you can use this plugin I found on the DNN store. https://store.dnnsoftware.com/dnn-rest-api-custom-api-authentication-authorization.

Resources