How to "log out" of the REST API?

I’ve got a web page that is hitting several Epicor APIs to produce info on the page. The Epicor server is using Basic authentication, so the user has to enter their user name and password directly on an Epicor (web) page. Since I’m using javascript to hit the API, I am detecting when the call fails and redirecting the user to the Epicor web page in a new window, which pops up the login; afterward my page then lets them retry.

However, it has come up that once the user logs in, they remain logged in, sometimes even after restarting the browser. (Although, this may be an artifact of modern “remember your session” features some people have enabled). In any case, it is desirable for security reasons to have a way to make the browser forget the user’s credentials. I have tried the usual trick of putting bogus credentials in my AJAX calls, and I do get a 401 back, but then if I immediately issue the normal request to the same URL it happily returns 200 with the old, valid credentials.

In some places I have read that server coordination may be needed. Is there any endpoint on the Epicor side that would assist in logging the user out on the browser side? I have looked through the API help and I found that it’s also possible to use token authentication, but I don’t see it mentioned for either one how to invalidate the credentials when done.

Basic Auth is being “phased out” by many providers, including Microsoft. I saw a nice video about Microsoft Identity (prepared for the M365 Developer Exam) that does a really nice job explaining token authentication. Just throwing that out there for anyone and not Michael.

Microsoft identity platform: Getting Started with Microsoft identity - YouTube

I haven’t tried it but there is a session service with a logout method:

Ice.LIB.SessionModSvc/Logout

1 Like

Thanks Mark, I’ll take a look at token authentication. I found the Logout endpoint and used “Try it out” and … it succeeded but didn’t seem to prevent me from accessing the page further. Also, I tried the adjacent “Login” endpoint and it returned what looked like might be a token - presumably inheriting my basic authentication to perform the login I guess?

Well this kind of brings up more questions than it answers.

It looks like Ice.LIB.SessionModSvc/Login is different from TokenResource.svc. The former returns a GUID looking identifier which I assume is a token of some kind, but the latter returns a much longer string which looks like base 64. I can present this as a “Bearer” token to API requests and it seems to work, BUT I don’t see any endpoints to revoke this token or to change its timeout from the default of 1 day. Under Ice.LIB.TokenServiceSvc I see that I can call IsTokenValid and it does tell me the token is valid, but nothing that I can see to revoke the token.

But Ice.LIB.SessionModSvc/Login on the other hand, what do I do with the “token” or whatever it’s return to me? I’ve thrown it into the authentication header with “Bearer”, "“Digest”, and “OAuth” without any luck. Likewise, calling Ice.LIB.SessionModSvc/Logout doesn’t seem to do anything.

Uh… I guess I can sort of revoke the TokenResource.svc token… as I have to provide it on each call myself, I can simply clear the variable value in Javascript. It doesn’t do anything to revoke it on the server side though, which might be a problem if this consumes a license.

@Epicorus, I hope you did not look at that as @Mark_Wonsil said you couldn’t. :joy: :rofl: :joy:

I get in enough trouble on my own without any help @jkane!

If I understand the immediate issue then Clear cookies\cache of your browser

Token is valid for one hour by default and it cannot be revoked as we don’t track tokens on server, just validates them.

SessionMod.Login creates session ID, and you can send it with each call to reuse same session. After that you can call SessionMod.Logout and session will be deleted from server.
But if you have valid token or use basic auth, it will just create new session single-call sesion on the next call, and not prevent you from login

1 Like

@Olga Where do I put the session ID in my requests?

(I tried setting a “sessionId” header but that didn’t work)

I’ve found a possible solution to this.

First of all, it seems the reason the browser is resistant to forgetting the credentials is because they are entered in the browser’s own pop-up dialog. If I prompt the user to enter their credentials in my own form on the page, and then present the credentials directly in the request headers (Authorization = base 64 of user name : password) then what I observe is that the browser doesn’t store the credentials at all, and I can “log off” simply by discarding them from wherever I am storing them.

But… there is another sticking point. If I send requests directly to some API functions I’m trying to call this way and the user mis-entered their credentials, the page returns a 401 AND sets the WWW-Authenticate header in the reply. This forces the browser to pop up it’s own dialog anyway, which is not what I want as it’s both confusing to the user (“I just entered my password, what is it asking for now?”) and if they do enter it correctly it will now be stuck into the browser and my logout code won’t work.

What I have found though is that the endpoints for TokenResource.svc (as well as SessionModeSrv) do NOT set the WWW-Authenticate header, so if the user enters the wrong credentials I can handle the 401 directly in my code and inform the user of what their mistake was and let them try again on my form instead of having the browser interfere.

What framework are you using to develop your web page? If it is MVC, you can clear the session and reset the session ID in the authentication token after the user logs out with the following bit of code from the logout controller action:

HttpContext.Current.Session.Clear();
HttpContext.Current.Session.Abandon();
HttpContext.Current.Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));

This will clear any session data from the server, and send back a new session cookie to the browser, with an empty session id. This way the browser has no way to reuse the session cookie after logout. If the credentials are saved they may be used to reopen a new session automatically by the browser though.

As for the SessionMod.svc endpoint, it uses Bearer authentication with a token. You first send a POST request to this URL with basic authentication: https://SERVER/DB/TokenResource.svc/ . This should respond with 200, carrying a token in a XML body:

<Token xmlns="http://schemas.datacontract.org/2004/07/Epicor.Web" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <AccessToken>
        eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNjExODExODgzIiwiaWF0IjoiMTYxMTgwODI4MyIsImlzcyI6ImVwaWNvciIsImF1ZCI6ImVwaWNvciIsInVzZXJuYW1lIjoiZXBpY2FkbWluIn0.23o2IoTbH0ucUUpqaUyUdxzcf8trZzQj2-DGhI0-H3A
    </AccessToken>
    <ExpiresIn>
        3600
    </ExpiresIn>
    <RefreshToken/>
    <TokenType>
        Bearer
    </TokenType>
</Token>

For all subsequent requests until logout, you do NOT include the username and password, instead add a Authorization header containing “Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleH…” to each subsequent request (along with your API key for version 2). When you call Logout, that token is no longer valid and the Epicor session is closed on the server. This ensures no Epicor sessions stay opened consuming licenses.

Note that for token authentication to work you need to enable it in the Epicor Administration Console:

Another thing to note is that you should keep track of the time you obtained the token at, and once 3600 seconds have passed (or whatever your lifetime is), also expire your web app’s session and force the user to log back in, which obtains a new token.

I’m just using plain javascript at the moment - I’m using the fetch API to send requests.

I’m a bit confused about your statement about SessionMod.svc… what you are describing is TokenResource.svc, which I do have working. SessionMod on the other hand returns a session ID which looks something like 12345678-9abc-5678-1234-123456789abc. But I don’t see any way to logout (invalidate) a token.

SessionMod logs out of the session, no matter where you got the token from (thus invalidating the token).

In pure JS I would do something like this:

function Login() {
    // Get new token with 3600 seconds lifetime from TokenResource.svc here, save it in global var
    sessionToken = 'myNewToken';

    // Call SessionMod/Login here with your token, get a session ID, save it:
    sessionId = 'someGUID'

    // for each subsequent request, Include the token AND the SessionInfo: {"SessionID": someGUID} header
   
    // Set timer to logout after 3600000 miliseconds
    sessionTimeout = window.setTimeout(Logout, 3600000); 
}

function Logout() {
    // Stop and clear the session timer
    window.clearInterval(sessionTimeout);

    // Call SessionMod.svc/Logout here, including both the token and SessionInfo headers, and clear saved authentication
    
    // Clear token variable
    sessionToken = '';
}

But you pass the session ID to log out, not the token, right?

It looks like for the session, you are saying that the session ID gets passed in a cookie named “ASP.NET_SessionId”.

No, you pass the token to logout. Just POST to SessionMod.svc/Logout with your token as authentication, and your session is immediately terminated.

The cookie thing was specific to a MVC or WebForms app, not applicable if you just use JS to forward the username and password to Epicor. (which BTW you should not be doing if you are exposing the web page to the internet; you need some isolation between Epicor REST services and the web…)

1 Like

Hmmm seems you were right… I did some tests, and the token authentication seems to be independent from the session management.

To logout, you have to POST to SessionMod/Logout including a header SessionInfo, containing the GUID you get from Login, like this: SessionInfo: {“SessionID”: 232a2868-cf60-46f1-8dba-1ac53328a66b}. This effectively closes the Epicor session.

However, that doesn’t invalidate the token! Seems the authentication token is still valid for its entire lifetime no matter what. In fact you can call Login again with the same token to reopen a new session. There doesn’t seem to be any way to invalidate the token.

So, the order of things is:
1- Call TokenResource.svc, get a new token
2- Call SessionMod/Login with your token, get a session ID
3- Include the SessionInfo: {“SessionID”: 232a2868-cf60-46f1-8dba-1ac53328a66b} header and the Authorization: Bearer TOKEN header for every subsequent request. (and API key for V2!)
4- When you are done, call SessionMod/Logout, still including both headers. This closes the Epicor session, but the token is still valid and you can reuse it to open a new session. You cannot however keep using the same SessionInfo header, services will return 400 something.
5- If you only include the token without the SessionInfo header, a new session is created with each request, same as if you were using basic authentication.

So, the solution is just to “forget” the token and let it expire once you’ve logged out, and to log back in with a new token and a new session if you get a 400.

I would really have expected the auth token to expire with the session though…

1 Like

Session header looks like:

“SessionInfo: {“SessionID”:“xxx-guid–”}”

But again it is not connected with token, only with session used by logged in user.

Tokens are not stored, so they cannot be expired . You can change token lifetime to be less then default 1 hour, that is it.

It could expire if it contained a hash of the session ID, which is usually what is done. Otherwise you could emit a token for 365 days, and even if you fired the employee the next day they will still have access to the DB for a year.