# Getting Logged in User on Client/Designer

**URL:** <https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228>\
**Category:** Module Development\
**Created:** [September 15, 2023, 4:39pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228 "2023-09-15T16:39:12Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [September 15, 2023, 4:39pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/1 "2023-09-15T16:39:12Z")

</div>

I have an RPC endpoint I'd like to protect by checking security levels/zones/roles etc. I wanted to check what the recommended way of handling this would be?

I'm thinking I'll use the GatewayConnectionManager and ClientUserUtilities to pass the entire User object, but is there an easier way to do this based on a session ID or something on the gateway when I receive an RPC request?

Also I haven't done much research on this, but what's the recommended way to check authorization with the new "Security Levels" that you guys have implemented?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [September 15, 2023, 4:51pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/2 "2023-09-15T16:51:37Z")

</div>

You get a ClientReqSession in your RPC handler invocation:

> <https://github.com/inductiveautomation/ignition-sdk-examples/blob/eb185f214b7f5aae0a6cccf24d10b5ceb83ba6e8/scripting-function/scripting-function-gateway/src/main/java/com/inductiveautomation/ignition/examples/scripting/GatewayHook.java#L44C19-L44C32>

From the session you can retrieve the user object:  
`ClientReqSession.User clientUser = (ClientReqSession.User) session.getAttribute(ClientReqSession.SESSION_USER);`

That `ClientUser` inner class has entrypoints to the info you care about:  
[https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.32/com/inductiveautomation/ignition/gateway/clientcomm/ClientReqSession.User.html](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.32/com/inductiveautomation/ignition/gateway/clientcomm/ClientReqSession.User.html)

To check authorization, you would build or retrieve a `PermissionsConfig` and then check its `isAuthorized` method against your user's security levels.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [September 15, 2023, 5:12pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/3 "2023-09-15T17:12:21Z")

</div>

> [@paul-griffith](#):
>
> ClientReqSession

If you create a class instance of your RPC implementation to carry this Session, you probably will want to use a cache of those instances instead of creating one on every RPC call. But, be careful with lifetimes. Both the cache and the implementation instance need to hold the Session with a weak reference or you will prevent the session from being garbage collected after the client/designer closes.

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [September 15, 2023, 5:47pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/4 "2023-09-15T17:47:37Z")

</div>

I'm assuming I'd be safe to use a ThreadLocal to hold my session variable here? This is still a thread-per-request because it's a servlet underneath?

Now my next question, if a user is calling a script in Perspective is there a similar mechanism I can use to get the session/user?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [September 15, 2023, 6:03pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/5 "2023-09-15T18:03:17Z")

</div>

> [@pturmel](#):
>
> If you create a class instance of your RPC implementation to carry this Session, you probably will want to use a cache of those instances instead of creating one on every RPC call. But, be careful with lifetimes. Both the cache and the implementation instance need to hold the Session with a weak reference or you will prevent the session from being garbage collected after the client/designer closes.

That's true, and a good callout.

You could also look it up from the calling thread, e.g.

```auto
        if (ClientReqSession.exists()) {
            ClientReqSession session = ClientReqSession.get();

```

You might also have good luck looking at `com.inductiveautomation.ignition.common.tags.model.SecurityContext#THREAD_LOCAL`.

If you're already depending on Perspective, there's `com.inductiveautomation.perspective.gateway.session.InternalSession#SESSION`, another ThreadLocal.

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [September 20, 2023, 5:54pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/6 "2023-09-20T17:54:51Z")

</div>

Is there a way to get an [AuthenticatedUser](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.32/com/inductiveautomation/ignition/common/user/AuthenticatedUser.html) from a [WebAuthUser](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.32/com/inductiveautomation/ignition/common/auth/web/WebAuthUser.html)? I guess I'm looking for a way to get Roles/Levels/Zones from a WebAuthUser in the end. Am I guaranteed to have a SecurityContext thread local whenever I have a WebAuthUser thread local set?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [September 20, 2023, 6:06pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/7 "2023-09-20T18:06:37Z")

</div>

How are you currently obtaining a `WebAuthUser`? It looks like there's a sibling class `com.inductiveautomation.ignition.common.auth.web.WebAuthUserContext` that has zone information. As I understand things, `WebAuthUser` is the 'base' class for an IdP login, where `AuthenticatedUser` is the 'base' class for "classic" login using user sources, so I don't think the two are interchangeable in the way you're describing.

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [September 20, 2023, 6:14pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/8 "2023-09-20T18:14:10Z")

</div>

I was either going to pull it from:

```auto
com.inductiveautomation.perspective.gateway.session.InternalSession#SESSION

```

or by looking up a session-id in the REST API for my app.

I guess let me back up - I'm really looking for a way to build a common/custom SecurityContext object that I can use that will have Zones/Roles/Levels, the User object, and any information required for the audit log (hostname, designer/client/perspective enum etc.). But I'd like to try to build one common object for both RPC and Perspective if possible...

---

<div class="post-metadata">

**Author:** ![jspecht](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jspecht/32/6465_2.png) [@jspecht](https://forum.inductiveautomation.com/u/jspecht)\
**Post date:** [September 20, 2023, 10:45pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/9 "2023-09-20T22:45:50Z")

</div>

> [@cody.tamaki](#):
>
> I guess let me back up - I'm really looking for a way to build a common/custom SecurityContext object that I can use that will have Zones/Roles/Levels, the User object, and any information required for the audit log (hostname, designer/client/perspective enum etc.). But I'd like to try to build one common object for both RPC and Perspective if possible...

You'll have to create your own facade, with implementations for both a `WebAuthUserContext` (IdP auth strategy) and `AuthenticatedUser` (classic user source auth strategy).

We had to do that with Vision Client and Designer sessions when we introduced IdP support for those systems in 8.1. The facade we introduced is interface `com.inductiveautomation.ignition.gateway.clientcomm.ClientReqSession.User`:

```auto
    public interface User {

        default String getUserName() {
            return getUser()
                .map(AuthenticatedUser::getUsername)
                .orElse("unauthenticated");
        }

        ImmutableCollection<String> getRoles();

        ImmutableCollection<String> getSecurityZones();

        ImmutableCollection<SecurityLevelConfig> getSecurityLevels();

        Optional<AuthenticatedUser> getUser();

        QualifiedPath getPath();

    }

```

We then have a `ClassicUser` implementation for classic auth which wraps an `AuthenticatedUser`:

```auto
    public static class ClassicUser implements ClientReqSession.User {

        private final AuthenticatedUser authenticatedUser;

        public ClassicUser(AuthenticatedUser authenticatedUser) {
            this.authenticatedUser = authenticatedUser;
        }

        @Override
        public ImmutableCollection<String> getRoles() {
            return ImmutableList.copyOf(authenticatedUser.getRoles());
        }

        @Override
        public ImmutableCollection<String> getSecurityZones() {
            List<?> securityZones = authenticatedUser.get(AuthenticatedUser.SecurityZones);
            return securityZones == null ? ImmutableList.of() : securityZones.stream()
                .filter(String.class::isInstance)
                .map(String.class::cast)
                .collect(ImmutableList.toImmutableList());
        }

        @Override
        public ImmutableCollection<SecurityLevelConfig> getSecurityLevels() {
            ImmutableCollection<String> roles = getRoles();
            ImmutableCollection<String> securityZones = getSecurityZones();
            SecurityLevelConfig rolesSecurityLevel =
                ReservedSecurityLevel.Roles.withChildren(roles.toArray(new String[0]));
            SecurityLevelConfig authenticatedSecurityLevel =
                ReservedSecurityLevel.Authenticated.withChildren(rolesSecurityLevel);
            SecurityLevelConfig securityZonesSecurityLevel =
                ReservedSecurityLevel.SecurityZones.withChildren(securityZones.toArray(new String[0]));
            return ImmutableSet.of(authenticatedSecurityLevel, securityZonesSecurityLevel);
        }

        @Override
        public Optional<AuthenticatedUser> getUser() {
            return Optional.of(authenticatedUser);
        }

        @Override
        public QualifiedPath getPath() {
            return authenticatedUser.getPath();
        }

    }

```

We also have an `IdpUser` implementation for IdP-based auth which wraps a `WebAuthUserContext`:

```auto
    private static class IdpUser implements ClientReqSession.User {

        private final WebAuthUserContext webAuthUserContext;
        private final boolean cancelled;

        private IdpUser(WebAuthUserContext webAuthUserContext, boolean cancelled) {
            this.webAuthUserContext = webAuthUserContext;
            this.cancelled = cancelled;
        }

        @Override
        public ImmutableCollection<String> getRoles() {
            return webAuthUserContext.getWebAuthUser()
                .map(WebAuthUser::getRoles)
                .orElseGet(ImmutableSet::of);
        }

        @Override
        public ImmutableCollection<String> getSecurityZones() {
            return webAuthUserContext.getSecurityZones();
        }

        @Override
        public ImmutableCollection<SecurityLevelConfig> getSecurityLevels() {
            return webAuthUserContext.getSecurityLevels();
        }

        @Override
        public Optional<AuthenticatedUser> getUser() {
            return webAuthUserContext.getWebAuthUser().map(user -> {
                BasicAuthenticatedUser authenticatedUser = new BasicAuthenticatedUser(
                    webAuthUserContext.getIdp().orElse(null),
                    user.getId(),
                    user.getUserName(),
                    user.getRoles()
                );
                user.getFirstName()
                    .ifPresent(firstName -> authenticatedUser.set(User.FirstName, firstName));
                user.getLastName()
                    .ifPresent(lastName -> authenticatedUser.set(User.LastName, lastName));
                user.getEmail().ifPresent(email -> authenticatedUser.setContactInfo(List.of(new ContactInfo(
                    ContactType.EMAIL.getContactType(),
                    email
                ))));
                authenticatedUser.set(AuthenticatedUser.SecurityZones, new ArrayList<>(getSecurityZones()));
                return authenticatedUser;
            });
        }

        private QualifiedPath doGetPath() {
            return QualifiedPath.of(
                WellKnownPathTypes.UserProfile, "unknown",
                WellKnownPathTypes.User, "unauthenticated"
            );
        }

        @Override
        public QualifiedPath getPath() {
            return getUser()
                .map(AuthenticatedUser::getPath)
                .orElseGet(this::doGetPath);
        }

    }

```

Vision Client and Designer sessions were originally designed for Classic auth so the facade interface getUser() method returns an `AuthenticatedUser`, which is a straight delegation to the underlying `AuthenticatedUser` that a `ClassicUser` wraps and `IdpUser` returns an `AuthenticatedUser` instance adapted from the `WebAuthUserContext` instance.

There is a similar adaptation for `ClassicUser#getSecurityLevels` where we have to build the security levels from the roles and zones set on the `AuthenticatedUser`, where as an `IdpUser` delegates to the `WebAuthUserContext#getSecurityLevels` method since IdP auth strategy speaks security levels natively.

Note: getUser() returns an `Optional` of `AuthenticatedUser` because in the IdP auth strategy, it's possible that the user might be _authorized_ to access a project without logging in. For example: if the project is Public or only protected with security zones, the user may not need to log in to access the project...

Hope that helps!

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [September 22, 2023, 5:39pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/10 "2023-09-22T17:39:35Z")

</div>

That helps a ton - thank you Joel.

Last (hopefully) question - is there any way I can detect if a script is being called from the gateway scope (tag change script etc. vs perspective session) so that I can inject a system security context? Where do you guys generally use a "system context" inside of Ignition? Just so I can keep my code consistent...

---

<div class="post-metadata">

**Author:** ![jspecht](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jspecht/32/6465_2.png) [@jspecht](https://forum.inductiveautomation.com/u/jspecht)\
**Post date:** [September 22, 2023, 10:56pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/11 "2023-09-22T22:56:30Z")

</div>

Assuming you are referring to the tag system's SecurityContext (`com.inductiveautomation.ignition.common.tags.model.SecurityContext`) - We typically assign system context to privileged automated things configured by privileged admins / designers. Think SFCs, Transaction Groups, Gateway timer scripts, etc. They are non-human things that are configured to perform some kind of tag operations. They are not associated with a human via some session (such as Gateway Web Interface, Vision Clients, Perspective Sessions, etc).

> Is there any way I can detect if a script is being called from the gateway scope (tag change script etc. vs perspective session) so that I can inject a system security context?

I'm not sure I understand the use case... you're dealing with an RPC function, right? So these are limited to Vision Client Sessions and Designer Sessions. You have everything you need from `ClientReqSession` that is passed to you. A script shouldn't be calling into your RPC function...

Edit: to clarify, your RPC handler is Gateway-scoped - you provide an instance of it by overriding `GatewayModuleHook#getRPCHandler`. So you are running in the context of the Gateway. The Platform will provide the ClientReqSession to you, which is essentially a Vision Client or Designer session (you can check which one by calling `ClientReqSession#isDesigner`).

You can get an instance of the `ClientReqSession.User` object that I mentioned above which is associated with the given session by calling:

```auto
ClientReqSession.User clientUser = (ClientReqSession.User) clientSession.getAttribute(ClientReqSession.SESSION_USER);

```

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [June 25, 2024, 8:04pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/12 "2024-06-25T20:04:14Z")

</div>

I guess what I'm trying to do here is secure a service endpoint (RPC function calls the service class method), but I would like that same method to be available for gateway scripts.

So I'll have something like system.tamaki.logSomething that could be called from the client or from a gateway script, and if it's a client/designer calling it I want to make sure they have the correct roles/levels etc., but if it's a gateway event script that thread local wouldn't exist.

---

<div class="post-metadata">

**Author:** ![jspecht](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jspecht/32/6465_2.png) [@jspecht](https://forum.inductiveautomation.com/u/jspecht)\
**Post date:** [June 26, 2024, 8:27pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/13 "2024-06-26T20:27:06Z")

</div>

Well your RPC function handler in the Gateway will know who the user is from the session (details above), so you will know how to create your SecurityContext from that code path.

In the case where you have a scripting function invocation on the Gateway side, you could get the current value of `SecurityContext.THREAD_LOCAL.get()`. If there is a value present, then the system from which the script was invoked had set the SecurityContext for you. If it is not set (`null`), then you have to decide what the default value should be (either system context or empty context in most cases).

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [June 26, 2024, 8:37pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/14 "2024-06-26T20:37:56Z")

</div>

That's exactly what I needed. Thank you Joel!

---

<div class="post-metadata">

**Author:** ![cody.tamaki](https://avatars.discourse-cdn.com/v4/letter/c/b487fb/32.png) [@cody.tamaki](https://forum.inductiveautomation.com/u/cody.tamaki)\
**Post date:** [August 5, 2024, 5:12pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/15 "2024-08-05T17:12:28Z")

</div>

Ok last last question (maybe). I'm doing some HTTP basic auth for my app, and would like to authenticate that user and put them in some sort of context... 2 Parts.

1. How can I authenticate a user against an idp?
2. Which auth context would be appropriate for this situation?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [August 6, 2024, 4:12pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/16 "2024-08-06T16:12:47Z")

</div>

> [@cody.tamaki](#):
>
> HTTP basic auth

Unfortunately basic auth and IdP’s don’t go together.  
You can’t authenticate a user against an IdP, you can only redirect the user to the IdP and have it inform you of the result.

> **[IdpAdapterManager](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.38-rc1/com/inductiveautomation/ignition/gateway/auth/idp/IdpAdapterManager.html)**
>
> declaration: package: com.inductiveautomation.ignition.gateway.auth.idp, interface: IdpAdapterManager

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [August 6, 2024, 4:18pm UTC](https://forum.inductiveautomation.com/t/getting-logged-in-user-on-client-designer/79228/17 "2024-08-06T16:18:55Z")

</div>

I’d recommend a two step approach:

1. Have an interactive method for users to authenticate using an IdP, and if successful, receive an API key.
2. Then, users can provide the API key (+ a username, probably) to hit your endpoint.
