How we can pass extra params (which we send as query params to endsession endpoint) to Logout when user is not authenticated in IDP SSO
I'm using latest Identity Server 4.
In the standard case, when client initiates a logout (by accessing endsession endpoint), everything works fine when we have information about the user (which is stored in a cookie and endsession endpoint can successfully read that). EndSession redirects to https://myidp/Account/Logout?logoutId=someId and we can get any parameter which was passed in query string to endsession endpoint
But when we try to do second logout from the client (and there is no authenticated user in cookie), logoutId parameter is not passed to Logout endpoint and there is no chance to get params which we sent in query string to endsession endpoint
The reason why we need this is simple:
suppose client clicked logout twice on 2 different pages (client's pages)
when user was logged out, we want to redirect it back to client URL OR to add some extra logic depending on params which we send to endsession endpoint. But as we don't get any params in Logout method - we don't know anything about the client and what to do with this logout request
For now as workaround of this problem I added middleware in .Net Core app, which validates LogoutId parameter. If it doesn't exist (the case when we will not be able to get initial parameters, as no logoutId was passed in the query string to logout method), I add manually query string parameters from my middleware to the redirected URL
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace IdentityProviderWebApp.Core
{
public class EndRequestMiddleware
{
private readonly RequestDelegate _next;
public EndRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
if (context.Request.Path.Value == "/connect/endsession")
{
var logoutUrl = context.Response.Headers["location"].ToString();
if (!logoutUrl.Contains("?"))
{
var fixedLocation = context.Response.Headers["location"] + context.Request.QueryString;
context.Response.Headers.Remove("location");
context.Response.Headers.Add("location", new StringValues(fixedLocation));
}
}
}
}
}
Register middleware code
app.UseMiddleware<EndRequestMiddleware>();
In AccountController Logout method get your expected variables as parameters of logout method
public async Task<IActionResult> Logout(string logoutId, string client_id, string redirect_uri)
In the Logout method get actual variable either from a valid context (if LogoutId exists) or use value which you receive in your Logout method
var logout = await _interaction.GetLogoutContextAsync(logoutId);
clientId = logout.Parameters.Get("client_id") ?? clientId;
redirectUri = logout.Parameters.Get("redirect_uri") ?? redirectUri;
Hope someone will find better approach
Related
I'm attempting to query Solr from Angular and routing the request through a Play Controller for security and using Play redirect to forward the request to Solr.
This seems to be working on Chrome but not on Safari/Firefox.
Angular ajax request
var solrUrl = '/solr';
storesFactory.getAdvancedMessages = function (searchCriteria, searchType) {
var filterQuery = solrQueryComposer(searchCriteria);
$log.warn(filterQuery);
return $http({
method: 'GET',
url: solrUrl,
params: { 'q': '*',
'fq': filterQuery,
'rows': 30,
'wt': 'json'}
}).
then(function(data, status, headers, config) {
$log.debug(data.data.response.docs);
return data.data.response.docs;
},
function(error){
$log.error(error.message);
});
Play Controller
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Security;
#Security.Authenticated(Secured.class)
public class SolrController extends Controller {
private static String solrUrl = "http://whatever.com:5185/solr/select/";
private static String queryPart = "";
public static Result forward(){
queryPart = request().uri().substring(5);
System.out.println(queryPart);
return seeOther(solrUrl+queryPart);
}
}
Play Route
GET /solr controllers.SolrController.forward()
First of all, I'd like to clarify what you're doing.
Play is not forwarding anything here, it's sending a redirect to the client, asking to fetch another URL. The client will send a request, receive a redirect, and send another request.
Which means:
this controller is not "forwarding" anything. It's just tells the client to go somewhere else. ("seeOther", the name speaks for itself).
It's not secure at all. Anyone knowing solr's URL could just query it directly.
since the query is performed by the client, it may be stopped by the cross-domain security policy.
Moreover, There's a HUGE race condition waiting to happen in your code. solrUrl and queryPart are static, therefore shared by all threads, therefore shared by all clients!!
There's absolutely no reason for queryPart to be static, and actually, there's absolutely no reason for it to be in this scope. This variable should be defined in the method body.
I'd also like to point out that request().uri().substring(5) is very brittle and is going to break if you change the URL in the route file.
In return seeOther(solrUrl+queryPart), queryPart arguments keys and values should also be URLencoded.
How to authenticate and redirect a user to his own page i.e to www.mysite.com/"user's email".
I am using the following algo which is not working...
userDB in User class:
Map<String,String> userdata=new HashMap<String,String>();
First my login process form :
#Path("/login")
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void login(
#FormParam("email") String emailc,
#FormParam("password") String pass,
#Context HttpServletResponse servletResponse
) throws IOException,RuntimeException {
User u1=new User();
pass=u1.getPassword();
emailc=u1.getEmailaddrs();
boolean checked=false;
boolean exists;
exists=u1.userdata.containsKey(emailc);
if(exists){
String mypass =u1.userdata.get(emailc);
if(mypass==pass){
checked=true;
}else{
checked=false;
}
}else{
checked=false;
}
if(!checked){
//User Doesn't exists
servletResponse.sendRedirect("http://localhost:8080/MySite/pages/Create_Profile.html");
}else{
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
}
}
createprofile
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newUser(
#FormParam("email") String email,
#FormParam("password") String password,
#Context HttpServletResponse servletResponse
) throws IOException {
User u = new User(email,password);
User.userdata.put(email,password);
}
Your usage of userdata [Map] looks wrong to me. Is it a part of user class, is it non static or static ?
If it is non static then every time you will do new User() .. that map will be initialized and it will have no data in it. Hence u1.userdata.containsKey(emailc); will be always false.
If you are using a hashmap as a temporary database for dev purposes then, make it static rather keep it in a different class like UserStore or some DB access layer. Exmaple below:
public class UserDAO(){
private static Map<String,User> userdata = new HashMap<String,User>();
public boolean hasUser(String email){
return userdata.contains(email);
}
public User saveUser(String email, String password ...){
//make user object save it in map and return the same
}
// more methods for delete and edit etc.
}
And use this in your REST layer classes like this
exists = userDao.hasUser(email);
Advantages :
Your problem will be solved.
Later on when you move to actual db implementation you will just have to change your UserDao code and rest application code will be just fine. -- Loose coupling :)
Also regarding forward using email
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
add the email parameter there in the url only, if thats what you want:
servletResponse.sendRedirect("http://localhost:8080/MySite/"+emailc);
UPDATE :
See the fundamental thing is that you get request parameters [email , password]. You check it whether it is present in map or not. Now what you are doing wrong here is you create a new user like this User u = new User(); and then get email and password from it emailc = u.getEmail();. This emailc will always be null and your userdata map will always return false for that. You have two choices :
Either set email and password in user object and then get the data from user object.
Use the email and password obtained from request parameters for your logic. Do not alter them
One good practice to follow while programming is that at all times think of your method parameters as final parameters.
UPDATE 2 :
if(mypass==pass){
checked=true;
}else{
checked=false;
}
Change == to equals method. String matching should be done by equals or equalsIgnoreCase method not ==.
You always create a new User without any parameters: User u1=new User();. All these User instances will have the same property values and probably exists is always false.
I have REST service requirements in which some calls require authentication and some don't. Absolutely no state is used, as the calls are all independent from one another. I have put something together which seems to work, but is this the right way to go about not using sessions?
This question is kind of related to my WCF question which is answered here.
Firstly I registered the authentication method:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}
));
I then attribute the respective calls (or service or DTO) with the Authenticate attribute:
[Authenticate]
public HelloResponse Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name + " with POST & Auth"};
}
I inherit from the BasicAuthProvider class which does the authentication:
public class CustomCredentialsAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return userName == "dylan" && password == "abc123";
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
session.IsAuthenticated = true;
//Important: You need to save the session!
authService.SaveSession(session, new TimeSpan(0,0,10));
}
}
As you can see, I do save the session but it times out after 10 seconds. This is the part that I'm sure can potentially be done better. It seems to work nicely though.
Is there a better way of doing what I'm trying to accomplish?
Is there also any way, due to the sessionless nature of these services, to remove the Auth, AssignRoles and UnassignRoles methods?
If you wanted to keep using ServiceStack's Authentication and Session support you could just add a response filter that clears the user session after the service is executed, e.g:
this.ResponseFilters.Add((req, res, dto) => req.RemoveSession());
Basically after each request is executed it clears the session, so no record of them having authenticated exists.
Otherwise you can just skip using ServiceStack's Authentication completely and just provide your own via RequestFitlers of FilterAttributes (which is essentially what SS Auth does under the hood).
I'm using the spring-security-facebook plugin for authentication. It works well, now I'm trying to use some functions of spring-social-facebook that needs authorization. From my controller, where can I get a valid accessToken (in order to create a FacebookTemplate object) ?
This is how I'm using the plugin:
1) I added a domain class, OAuthUser (not in the plugin but in my project)
2) I have generated a FacebookAuthDaoImpl
3) I edited the methods generated, for example in create(), I create
+ an instance of a user (main domain class SecUser) and set the profile infos
+ a new OAuthUser (where I set the uid, the accessToken, and relate it to the main user created.
UPDATE 1:
I added those 3 methods in my FacebookAuthDaoImpl class:
Boolean hasValidToken(OAuthUser user){
def now= new Date()
if(now.after(user.accessTokenExpires)){
return false
} else {
return true
}
}
void updateToken(OAuthUser user, FacebookAuthToken token){
user.accessToken = token.accessToken
user.save()
}
String getAccessToken(OAuthUser user){
return user.accessToken
}
But I still have the expired AccessToken.
If you're using default DAO:
It's stored at field accessToken of your domain object for Facebok User.
If you don't have field named accessToken, you should add it (String accessToken). Ideally with an additional field: Date accessTokenExpires. And both fields will be automatically filled by the plugin.
If you've created your own DAO implementation, then:
create(FacebookAuthToken token) pass access token as token.accessToken.accessToken. You can store it anywhere you wish
Boolean hasValidToken(F user), void updateToken(F user, FacebookAuthToken token) and getAccessToken(F user) - first should check token expiration, second update with new value (called when token is expired) and last should retourn current value.
As you said, you have you own DAO implentation. How you're implemented last 3 methods?
I have been working with social auth API in JSF, for getting an login with facebook, I dont know whether am creating a session properly. I have searched some stuffs form internet and doing it. Now i have caught up with some error.
according to social auth first we need to call a function, which helps to get the authentication URL from the Facebook, where i need to create a session and and set attributes to it, with the parameters from facebook once user logs in into the facebook.
so my first view page will just contain,
a command button to call the respective function.
<h:form><h:commandButton value="submit" action="#{socialNetworkAuthentication.facebookAuthentication}"></h:commandButton></h:form>
then the function which is called ...
public String facebookAuthentication(){
try{
//Create an instance of SocialAuthConfgi object
SocialAuthConfig config = SocialAuthConfig.getDefault();
String propUrl="oauth_consumer.properties";
//load configuration. By default load the configuration from oauth_consumer.properties.
//You can also pass input stream, properties object or properties file name.
config.load(propUrl);
//Create an instance of SocialAuthManager and set config
SocialAuthManager manager = new SocialAuthManager();
manager.setSocialAuthConfig(config);
//URL of YOUR application which will be called after authentication
String successUrl = "http://chennaivolunteers.com/ChennaiVolunteers/faces/cv/employee-profile.xhtml";
// get Provider URL to which you should redirect for authentication.
// id can have values "facebook", "twitter", "yahoo" etc. or the OpenID URL
String url = manager.getAuthenticationUrl("facebook", successUrl);
// Store in session
HttpServletRequest request=(HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpSession ses = request.getSession(true);
ses.setAttribute("authManager", manager);
System.out.println(url);
FacesContext.getCurrentInstance().responseComplete();
FacesContext.getCurrentInstance().getExternalContext().redirect(url);
}
then finally i redirect to the respective URL given by the facebook, after user logs in into the facebook, then it automatically moves to the succesURL which is was mentioned in the above code.
In my successURL i just have an outputtext.
<tr><td class="profile-head"><h:outputText id="name" value="#{employeeProfile.profileName}" /></td></tr>
<tr><td class="content-black">
<div class="padding-2"><h:outputText id="name" value="#{employeeProfile.profileName}" />
In the backing bean, i created a session to get the attributes which i set earlier.
public class EmployeeProfile {
public String profileName;
public String getProfileName() throws Exception {
HttpServletRequest request=(HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpSession ses = request.getSession(true);
SocialAuthManager m = (SocialAuthManager)ses.getAttribute("authManager");
AuthProvider provider = m.connect(SocialAuthUtil.getRequestParametersMap(request));
Profile p = provider.getUserProfile();
String userName=p.getFirstName();
System.out.println(userName);
return userName;
}
public void setProfileName(String profileName) {
this.profileName = profileName;
}
when i printed the username in console it does, but its not the view page of this backing bean, have caught up with two exceptions as below.
1. javax.faces.FacesException: Could not retrieve value of component with path : {Component-Path : [Class: javax.faces.component.UIViewRoot,ViewId: /cv/employee-profile.xhtml][Class: javax.faces.component.html.HtmlOutputText,Id: name]}
Caused by: javax.el.ELException: /cv/employee-profile.xhtml at line 133 and column 105 value="#{employeeProfile.profileName}": Error reading 'profileName' on type socialServlet.EmployeeProfile
2.javax.faces.FacesException: This is not the same SocailAuthManager object that was used for login.Please check if you have called getAuthenticationUrl() method before calling connect()
Caused by: This is not the same SocailAuthManager object that was used for login.Please check if you have called getAuthenticationUrl() method before calling connect()
Last one is just an inbuilt exception of social auth API,
But i dont think any problem in this, because when i try it out with servlet, everything works fine, i think am doing some mistake in JSF session. But i dont know wher i am wrong.
My problem is now rectified.. the mistake i did is, i have invoked the connect() function again. Now i have did everything in the constructor itself. It works fine.
public class EmployeeProfile {
public EmployeeProfile() throws Exception {
// TODO Auto-generated constructor stub
ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
HttpServletRequest request = (HttpServletRequest)ectx.getRequest();
HttpSession session = request.getSession(true);
SocialAuthManager m = (SocialAuthManager)session.getAttribute("authManager");
AuthProvider provider = m.connect(SocialAuthUtil.getRequestParametersMap(request));
Profile p = provider.getUserProfile();
String userName=p.getFirstName();
System.out.println(userName);
setProfileName(userName);
setBirthDate(p.getDob());
setImageUrl(p.getProfileImageURL());
p.getGender();
}