What's the correct flow the OneLogin Login API - api

I'm busy writing a test harness that will validate a REST API I've been developing. In the normal usage the my REST API will be used by a Web application that is secured and authenticated by OneLogin. My test harness will therefore have to use OneLogin to authenticate before I can call my own functions. I'm struggling to understand the right workflow for logging through an API as my test harness does not have a browser. So far I've got:
Request an Authentication Token using the Client Id and Secret (https://developers.onelogin.com/api-docs/1/oauth20-tokens/generate-tokens)
Request a Session Token using the Authentication Token, Sub-Domain, user name and password (https://developers.onelogin.com/api-docs/1/users/create-session-login-token)
I'm not quite sure what do do with the Session Token. I suspect I might have to create a session (https://developers.onelogin.com/api-docs/1/users/create-session-via-token) but that appears to be using a different URL.
Any ideas?
Updated:
Here is the code that I'm using for that final step. I pass in the Session Token obtained in the previous step.
/** see https://developers.onelogin.com/api-docs/1/users/create-session-via-token */
public void createSession(String sessionToken) {
HttpPost httpPost = new HttpPost("https://admin.us.onelogin.com/session_via_api_token");
List<NameValuePair> postParameters = new ArrayList<>();
postParameters.add(new BasicNameValuePair("session_token", sessionToken));
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpEntity entity = new UrlEncodedFormEntity(postParameters);
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
int status = response.getStatusLine().getStatusCode();
System.out.println(status);
} catch (Exception e) {
e.printStackTrace();
}
}
The status code returned is 302 and the response headers contain Location: https://<my-company>.onelogin.com.
I'm taking this to mean that the last call has failed for some reason and I'm being redirected back to the log-in page.

You suspect right.
Basically, after your server makes the back-channel requests for the token, you have to hand the token off via the front channel (user's browser) so you can establish a session, get cookies, etc... using the session_via_api_token 'endpoint' (which is a totally different sort of API from the ones to get the token in the first place)

Related

Blazor WASM - User.Identity?.IsAuthenticated == true but unable to get access token without user intervention

I have a dotnet 7 Blazor WASM app, using Azure AD B2C (via AddMsalAuthentication in Program.cs).
The homepage of the app allows anonymous access, and features a call-to-action to login if the user is not authenticated.
In the layout used by the homepage, I have a dropdown that is populated via an API call. This will attempt to make the API call if User.Identity?.IsAuthenticated == true. The API call uses an HTTP client using BaseAddressAuthorizationMessageHandler (which in turn inherits AuthorizationMessageHandler) which is responsible for silently obtaining an access token before making the call.
If obtaining an access token fails, AccessTokenNotAvailableException is thrown and I call Redirect() on the exception, which redirects to the B2C login screen. This is not the behaviour I want as users are redirected without responding to the call-to-action to log in.
Delving into the library code, this seems to be where Blazor WASM creates the ClaimsIdentity.  It calls a JS method in MSAL.js, AuthenticationService.getUser:
If I call the AuthenticationService.getUser method in my browser, I can see an object returned:
I note the exp claim for the user is a few days ago - in my mind this has expired.
I also notice ClaimsIdentity.IsAuthenticated returns true when the AuthenticationType is non-null. In my case, the AuthenticationType is a Guid matching my B2C app's ClientId.
So my question is: what should I be calling, other than User.Identity?.IsAuthenticated == true to determine whether the user is authenticated, and an access token can be provisioned without the user having to re-authenticate? Should I be using Blazor custom policies?
After some more investigation, my conclusion is this behaviour is by design.
The default policy out-of-the box simply adds RequireAuthenticatedUser:
This doesn't necessarily do what you'd expect - it adds DenyAnonymousAuthorizationRequirement:
Which in turn simply checks that the Identity is authenticated (which as I noted before just checks for a non-null authentication type):
My solution was to use a custom policy - in services.AddAuthorizationCore - to check the exp claim:
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("email")
.AddAuthenticationSchemes("bearer")
.RequireAssertion(ctx =>
{
var exp = ctx.User.Claims.SingleOrDefault(c => c.Type.Equals("exp" , StringComparison.OrdinalIgnoreCase));
if (exp is null)
return false;
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(exp.Value));
return datetime.UtcDateTime > Clock.Instance.UtcNow;
});

Session lost after redirect SAML: may be different webapps?

I am fairly new to webapps programming, so I thought of asking here.
I am implementing the SAML2 protocol in an open source app (namely OFBiz) but I am encountering a problem related to session loss after the protocol made its course.
I am following these steps to implement the protocol. Suppose ofbizwebsite.com is the URL of the site.
Installed a custom plugin named SAMLIntegration which exposes the ACS page and the logic for login. To my understanding, a plugin (gradle) is like an indipendent java project, which translates to a new set of resources for the application (the plugin enables, for example, to visit ofbizwebsite.com/SAMLIntegration and setup some resources).
Exposed the ACS page to ofbizwebsite.com/SAMLIntegration/control/acs, as well as metadata ofbizwebsite.com/SAMLIntegration/control/metadata.jsp
Created the logic for login. Basically, an entity called UserLogin is saved in the session and recovered by a "checker" to understand if an user is logged in. Suppose that this checker is a HTTP WebEvent handler which can be called by any resource requiring authentication.
Now, the problem. If redirect the user to a resource on SAMLIntegration (for example ofbizwebsite.com/SAMLIntegration/control/aview or any ofbizwebsite.com/SAMLIntegration/control/* by calling response.sendRedirect("aview")) check works and login is preserved. Visiting any resource (for example ofbizwebsite.com/aplugin/control/anotherview) by navigating the application does not preserve the session.
OFBiz use internally a mechanism for preserving the userLogin between webapps, by creating an HashMap between and UUID and a UserLogin object. The UUID is passed between two different resources, appending this key to each path (so ofbizwebsite.com/aplugin/control/anotherview?externalKey=THEEFFECTIVEUUID)
To my understanding, changing from ofbizwebsite.com/SAMLIntegration/control/* to ofbizwebsite.com/aplugin/control/* determine a session loss. So, my idea was to replace the UUID mechanism with SAML2. However, I do not know how to solve this problem.
In particular, I would like to execute a SAML request each time the checker function is executed. If I can't find the user in the session, a SAML request is fired. However, my problem is HOW to manage the response. Normally, I would redirect it to the acs ofbizwebsite.com/SAMLIntegration/control/acs. Doing so, however, does not allow me to handle the response in the checker function, as the control is passed to another servlet by an external request (the SAML response fired by the IdP). Should I provide a different acs for each different path? (so one for SAMLIntegration and one for aplugin?) And, even if this was the case, how can I return the control to the checker function which has invoked the SAML request?
Here you go for installing the Shibboleth HTTPD module: https://pad.nereide.fr/SAMLWithShibboleth
You also need this method somewhere in OFBiz (I recommend LoginWorker.java, but you can put it where you want). It allows to use the externalAuthId of the userLogin for authentication, with the uid returned by the sso:
public static String checkShibbolethRequestRemoteUserLogin(HttpServletRequest request, HttpServletResponse response) {
LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher");
Delegator delegator = dispatcher.getDelegator();
// make sure the user isn't already logged in
if (!LoginWorker.isUserLoggedIn(request)) {
String remoteUserId = (String) request.getAttribute("uid"); // This is the one which works, uid at Idp, remoteUserId here
if (UtilValidate.isNotEmpty(remoteUserId)) {
//we resolve if the user exist with externalAuthId
String userLoginId = null;
GenericValue userLogin;
try {
List<GenericValue> userLogins = delegator.findList("UserLogin",
EntityCondition.makeConditionMap("externalAuthId", remoteUserId, "enabled", "Y"),
null, null, null, true);
userLogin = userLogins.size() == 1 ? userLogins.get(0) : null;
} catch (GenericEntityException e) {
Debug.logError(e, module);
return "error";
}
if (userLogin != null) {
userLoginId = userLogin.getString("userLoginId");
}
//now try to log the user found
return LoginWorker.loginUserWithUserLoginId(request, response, userLoginId);
}
}
return "success";
}
You also need to have this method as an OFBiz preprocessor in the webapp controllers. I suggest to have a look at common-controller.xml.
Finally you need the configs that redirect to the SSO page if no session. That should do the job, at least it works for them...
Finally I recommend https://www.varonis.com/blog/what-is-saml in case of need.

Create a space with Google Chat REST API

We have a program that uses a service account to manage various thing inside Google Chat.
Now, we have the need to create a new space using the Google Chat REST API (spaces.create).
We already joined the developer preview program, as this endpoint is not yet generally available.
From what we understand, this endpoint is not possible to invoke via service account and so we wanted to ask you… can we invoke this endpoint automatically using “domain delegation”? If yes, how?
We always want to use the service account as it is not possible to show a login prompt to the user.
We enabled the domain delegation but that endpoint returns always status 403. (We are using Google.Apis library for .NET Core
using Google.Apis.Auth.OAuth2;
var credential = GoogleCredential.FromFile("key.json")
.CreateScoped("https://www.googleapis.com/auth/chat.spaces.create")
.CreateWithUser("service-account-email#project.iam.gserviceaccount.com");
var token = await credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
HttpRequestMessage request = new(HttpMethod.Post, "https://chat.googleapis.com/v1/spaces");
request.Headers.Authorization = new("Bearer", token);
var payload = #"
{
""name"": ""testspace-1"",
""spaceType"": ""SPACE"",
""singleUserBotDm"": true,
""displayName"": ""Test Space""
}
";
request.Content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
HttpClient client = new();
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
If you check the documentation Auth
You will notice it stats that service account authorization is supported. If you are part of thedeveloper preview program
The reason I asked about your code is that the last time i tried this which notably was a while ago. The google .net client library was not generated the methods that were under preview.
So while it may work yes. The issue your may have is that the client library when loaded will not have the method you need meaning you will have to code the call to the endpoint yourself.
If this is in fact the case let me know if you have any issues peceing it together I may be able to help.
update your code
There is an error in your code
.CreateWithUser("service-account-email#project.iam.gserviceaccount.com");
The email in CreateWithUser should be the user on your domain who you wish to delicate the service account as. Not the service account email address.

OWIN cookie authentication OnValidateIdentity should be called on every request?

Perhaps I am misunderstanding how this works, but I thought the OnValidateIdentity would be called on every HTTP request to the server. I see it get hit on initial login (a bunch of times, so it looks like it is every HTTP request on initially), but then it doesn't seem to get called again. I let my app sit there until it should be expired, but it never gets hit again on subsequent requests to the server, until I logout and login again.
I have set my expiry very low and turned off sliding expiration to see if I could get it to fail, but to no avail.
Should I not see the OnValidateIdentity get called on every HTTP request? Ultimately, I really just want a cookie expiry to result in a logout. I assumed I had to check the expiry on each request in the OnValidateIdentity, but if this is not the way to do it please let me know!
Am I misunderstanding how this work, or how I should be using cookie expiries to force a logout?
Here is my basic setup:
app.UseCookieAuthentication(New CookieAuthenticationOptions() With
{
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.AuthenticationMode = AuthenticationMode.Active,
.ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
.SlidingExpiration = False,
.Provider = New CookieAuthenticationProvider() With
{
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser, Integer)(
validateInterval:=TimeSpan.FromMinutes(1.0),
regenerateIdentityCallback:=Async Function(manager, user)
Return Await user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie)
End Function,
getUserIdCallback:=Function(id) id.GetUserId(Of Integer)())
},
.LoginPath = New PathString("/Account/Login")
})
Probably a bit late...
To use cookie Expiry, that's delegated to the browser. If a cookie expires, then the browser wouldn't send it.
OnValidateIdentity is called on every request. If you want to debug it, pull it out into a new method and debug from that method instead.
However, the identity itself is only validated against the database when the validationInterval is passed.

What method should I use for a login (authentication) request?

I would like to know which http method I should use when doing a login request, and why? Since this request creates an object (a user session) on the server, I think it should be POST, what do you think? But since the login request should be idempotent, it could be PUT, couldn't it?
Same question for a logout request, should I use the DELETE method?
If your login request is via a user supplying a username and password then a POST is preferable, as details will be sent in the HTTP messages body rather than the URL. Although it will still be sent plain text, unless you're encrypting via https.
The HTTP DELETE method is a request to delete something on the server. I don't think that DELETING an in memory user session is really what it's intended; more it's for deleting the user record itself. So potentially logout can be just a GET e.g. www.yoursite.com/logout.
I believe that you can translate LOGIN & LOGOUT methods into basic CRUD operations CREATE & DELETE. Since you are creating a new resource called SESSION and destroying it when logging out:
POST /login - creates session
DELETE /logout - destroys session
I would never do LOGOUT as GET just because anyone could make an attack just simply by sending an email with IMG tag or link to website where such an IMG tag exists. (<img src="youtsite.com/logout" />)
P.S.
Long time I was wondering how would you create a RESTful login/logout and it turned out it's really simple, you do it just like I described: use /session/ endpoint with CREATE and DELETE methods and you are fine. You could also use UPDATE if you want to update session in one way or another...
Here is my solution based on REST guides and recommendations:
LOGIN - create a resource
Request:
POST => https://example.com/sessions/
BODY => {'login': 'login#example.com', 'password': '123456'}
Response:
http status code 201 (Created)
{'token': '761b69db-ace4-49cd-84cb-4550be231e8f'}
LOGOUT - delete a resource
Request:
DELETE => https://example.com/sessions/761b69db-ace4-49cd-84cb-4550be231e8f/
Response:
http status code 204 (No Content)
For login request we should use POST method. Because our login data is secure which needs security. When use POST method the data is sent to server in a bundle. But in GET method data is sent to the server followed by the url like append with url request which will be seen to everyone.
So For secure authentication and authorization process we should use POST method.
I hope this solution will help you.
Thanks
Regarding the method for logging out:
In the Spring (Java Framework) documentation, they state that a POST request is preferred, since a GET makes you vulnerable to CSRF (Cross-Site Request Forgery) and the user could be logged out.
Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.
See: https://docs.spring.io/spring-security/site/docs/current/reference/html/web-app-security.html#csrf-logout
Logging in should also use POST (body can be encrypted, see the other answers).
For Login I use POST, below is my code for LOGIN method
I used Nodejs with Express and Mongoose
your router.js
const express = require("express");
const router = express.Router();
router.post("/login", login);
your controller.js
export.login = async(req, res) => {
//find the user based on email
const {email, password} = req.body;
try{
const user = awaitUser.findOne({email});
if(user==null)
return res.status(400).json({err : "User with
email doesnot exists.Please signup"});
}
catch(error){
return res.status(500).json({err :
error.message});
}
//IF EVERYTHING GOES FINE, ASSIGN YOUR TOKEN
make sure you have JWT installed
const token = jwt.sign({_id: user._id}, YOUR_SECRET_KEY);
res.cookie('t');
const {_id, name, email} = user;
return res.json({token, user : {_id, email, name}});
}