Session Cookie 101

Hello,

I’ve established a basic token based authentication workflow but I want to switch to a session-based workflow using cookies so that my client doesn’t have to keep stuffing a bearer token in each request.

I’m using Express JS on the server and for the sake of this Q&A, I’m running everything locally on my machine. My client is running on localhost:8000 and my server is running on localhost:3000 . I’m using the okta-auth-js SDK and the authClient.session.setCookieAndRedirect() method in simple JavaScript as my Hello World example below.

To the point:

Q1: How do I get Okta’s setCookieAndRedirect() to set an HttpOnly cookie in my domain vs the oktapreview domain?

I see a cookie with the session ID set in the mydomain.oktapreview.com domain using my browser but I don’t see it in my current application’s domain. As such, when I submit a call to my back-end API from my application domain, there is no cookie seen by the server.

Q2: Should my server expect to see a session ID in a request’s HttpOnly cookie?

Here’s what I’ve done in the client. At this point, all the server does is dump out the request headers and cookies:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://ok1static.oktacdn.com/assets/js/sdk/okta-auth-js/1.8.0/okta-auth-js.min.js" type="text/javascript"></script>
<script type="text/javascript">
    var authClient = new OktaAuth({
        responseType: ['token','id_token'],
        scopes: ['openid','email'],
        url: 'https://mydomain.oktapreview.com',
        clientId: 'my-client-id',
        responseMode: 'fragment',
        redirectUri: 'http://localhost:8000',
        issuer: 'https://mydomain.oktapreview.com/oauth2/default'
    });


    function signInAndSetCookie() {
        console.log('signInAndSetCookie()');
        authClient.signIn({
            username: 'mytestuser',
            password: 'mytestuserspassword'
        })
        .then(function(transaction) {
            // If successful, save the access and ID tokens in the tokenManager for easy retrieval later.
            if (transaction.status === 'SUCCESS') {
                console.log('transaction.sessionToken => ' + transaction.sessionToken);
                authClient.session.setCookieAndRedirect(transaction.sessionToken,'http://localhost:8000/index.html');
            } else {
                throw 'We cannot handle the ' + transaction.status + ' status';
            }
        })
        .fail(function(err) {
            console.log("login fail: "+err.message)
            console.error(err);
        });
    }


    function listCatalogLocalhost() {
        console.log('listCatalog()');
        $.ajax({
            url: 'http://localhost:3000/api/list/get_catalog',
            success: function(response) {
                // Received messages!
                console.log('Messages', response);
            },
            error: function(response) {
                console.error(response);
            }
        });
    }

</script>

<body>
<a href="javascript:signInAndSetCookie()"  id="signin">Sign In and Set Session Cookie</a><br>
<a href="javascript:listCatalogLocalhost()"      id="loadlistslocalhost">Load Lists (localhost:3000)</a><br>
</body>

Your help would be greatly appreciated!!

–Ray

My citations and prior research:

Documentation
https://developer.okta.com/use_cases/authentication/session_cookie#retrieving-a-session-cookie-via-openid-connect-authorization-endpoint

Prior related posts:


Hi @RayRenteria, thanks for the question!

The cookie you see on the Okta domain is for single-sign-on purposes and isn’t meant to be used for a session that is local to your application. If you want your application to have a session I would suggest using our OIDC Middleware library. This library invokes the auth code flow with Okta, and after verifying the redirect it creates a cookie-based application session for your Express application.

Here is an example that shows you how to use that library:

That repository also has an example that uses the sign-in widget. I hope this helps!

1 Like

Thanks @RobertJD! I got the sample up and running and successfully observed a correctly-set session cookie. I have a good reference implementation now and will study it to apply to my app. I’ll report back with a synopsis for other noobs (like me) when I’ve got my own app up and running with the same authentication flow.

Thanks again for pointing me in the right direction!

–Ray

Thanks again Robert for the pointer.

To narrow the scope of my question, it pertains to ExpressOIDC() custom routes and session.setCookieAndRedirect() .

I’ve been able to successfully get all the essentials in my own app but I’m having trouble with getting the callback.defaultRedirect route callback to fire. I verified that my client correctly calls /authorization-code/callback as the session id is properly set in the cookie; however, the client (localhost:8080) keeps getting redirected to the root of my API server (localhost:3000).

Q: How can I specify a redirect after a successful /authorization-code/callback ?

Here’s my OIDC configuration on localhost:3000:

    const oidc = new ExpressOIDC(Object.assign(
        {
            issuer: okta_settings.oidc.issuer,
            client_id: okta_settings.oidc.clientId,
            client_secret: okta_settings.oidc.clientSecret,
            redirect_uri: okta_settings.oidc.redirectUri,
            scope: okta_settings.oidc.scope
        },
        {
            routes: {
                login: {
                    viewHandler: (req, res, next) => {
                        // This works as expected
                        res.redirect('http://localhost:8080/');
                    }
                }
            },
            callback: {
                path: '/authorization-code/callback',
                handler: (req, res, next) => {
                    res.redirect('http://localhost:8080/');  // Does not work.
                },
                defaultRedirect: 'http://localhost:8080/'    // Does not work.
            }
        })
    );

My login page will not be served up by my API server. My application is running on an app subdomain while my API is running on an api subdomain. Both use the same base domain. Locally, I’m simulating this by running a stand-alone and simple index.html on localhost:8080 while my API server is running on localhost:3000 . I don’t want any redirects to go to localhost:3000.

My client is different than the reference implementation, but here’s the OktaAuth() configuration:

    var authClient = new OktaAuth({
        responseType: ['token','id_token'],
        scopes: ['openid','email'],
        url: 'https://{my-dev-domain}.oktapreview.com',
        clientId: '{my-client-id}',
        responseMode: 'fragment',
        redirectUri: 'http://localhost:3000/authorization-code/callback',
        issuer: 'https://{my-dev-domain}.oktapreview.com/oauth2/default'
    });

And here’s my actual client-side authentication function:

    function signInAndSetCookie() {

        authClient.signIn({ username: '{testuser}', password: '{testpassword}' })
        .then(function(transaction) {

            if (transaction.status === 'SUCCESS') {
                authClient.session.setCookieAndRedirect(transaction.sessionToken,'http://localhost:3000/authorization-code/callback');
            } else {
                throw 'We cannot handle the ' + transaction.status + ' status';
            }

        })
        .fail(function(err) {
            console.log("login fail: "+err.message)
            console.error(err);
        });
    }

Except for the final redirect, the authentication works as expected. The cookie contains the session ID and I’ve been able to verify proper authorized and non-authorized calls to my API server.

I’d appreciate any help you might be able to provide here on why I can’t specify the proper redirect after the authorization-code/callback.

Thank you,

–Ray

For other noobs like me

One thing that really sent me on a wild goose chase was the “redirectUri” attribute for the authorization-code/callback . I thought that I could specify the page that a user would go to upon successful login. That’s not what it is. It’s an important hook that correctly sets the session id in the HttpOnly cookie. Pulled my hair out on this one for way too long until I read the oidc.router section which says:

/authorization-code/callback - processes the OIDC response, then attaches userinfo to the session

My citations and research:

I found the problem.

I had a dumb syntax error in my JSON for the custom routes. Note the improper placement of the callback attribute:

WRONG! :

    const oidc = new ExpressOIDC(Object.assign(
        {
            issuer: okta_settings.oidc.issuer,
            client_id: okta_settings.oidc.clientId,
            client_secret: okta_settings.oidc.clientSecret,
            redirect_uri: okta_settings.oidc.redirectUri,
            scope: okta_settings.oidc.scope
        },
        {
            routes: {
                login: {
                    viewHandler: (req, res, next) => {
                        // This works as expected
                        res.redirect('http://localhost:8080/');
                    }
                }
            },
            callback: {                   // <-- DOES NOT BELONG HERE!!
                path: '/authorization-code/callback',
                handler: (req, res, next) => {
                    res.redirect('http://localhost:8080/');  
                },
                defaultRedirect: 'http://localhost:8080/' 
            }
        })
    );

RIGHT! :

    const oidc = new ExpressOIDC(Object.assign(
        {
            issuer: okta_settings.oidc.issuer,
            client_id: okta_settings.oidc.clientId,
            client_secret: okta_settings.oidc.clientSecret,
            redirect_uri: okta_settings.oidc.redirectUri,
            scope: okta_settings.oidc.scope
        },
        {
            routes: {
                login: {
                    viewHandler: (req, res, next) => {
                        // This works as expected
                        res.redirect('http://localhost:8080/');
                    }
                },
                callback: {
                   // This works as expected now that I got it in the right place!
                   defaultRedirect: 'http://localhost:8080/' 
               }
            }
        })
    );

Thanks again @robertjd for your help. I would have been done much sooner had I caught my syntax error above.

–Ray