2

In the Medium app, if you go to the settings tab, you can change some of the settings inside the app. But, if you try to change your email notification settings, it opens up a web browser inside the app and automatically logs you in to the Medium website so that you can change your settings there.

I want to do the same thing in my Ionic React app that talks to a Drupal 8.9 server.

My Drupal site is fully functional and offers some complicated forms; my Ionic app is more limited and I don't want to re-implement everything. So if the user needs a rare or unusual feature, I want to let them open up my website (and automatically log them in) from within the app.

I use JSON:API to query the database, and Simple OAuth (OAuth2 to handle logging in.

So, I somehow need to get the Drupal session cookie when the user logs in to my site via OAuth through the Ionic app (mobile app on Android + iOS).

How can I get a session cookie to log the user in to the actual Drupal site when logging in from a decoupled app?

Desired flow

  1. User logs in inside the Ionic app.
  2. User does stuff inside the app.
  3. User sometimes clicks a link to my Drupal site and is still logged in to the site because I am using a Drupal session token.

The problem with OAuth is that if I click a link to a page on my Drupal site (outside my app), then I am not logged in on that page.

In my case, I really need to be able to easily redirect from inside my app to the Drupal site and stay logged in, so I want to use the Drupal session cookie for this.

Where I'm stuck is how do I get the session cookie from Drupal using Javascript?

The jsonapi documentation gives this example:

`curl --header "Content-Type: application/json" -c cookie.txt 
--request POST "https://example.com/user/login?_format=json" 
--data '{"mail": "t[email protected]", "pass":"password"}'`

When I do this, here's the content of cookie.txt

 Netscape HTTP Cookie File
# https://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_.example.com      TRUE    /       TRUE   1615666425      SESSf6f36d8c129ea800a6658d30964d3887    PTgk_Lgm9yLwQ3NhYtdexnexuKSJ-TItAWSkxPiUPoU

Cool, this is the drupal session cookie.

However, I only have access to Javascript, not curl, in React. Also, I'm thinking that I may be over-thinking this and there is a really simple way to get the session cookie that I have overlooked.

So, how do I get the login session cookie via Javascript?

I tried to do this with the Fetch API:

  async function login(email: string, password: string): Promise<void> {
    const loginPost = {
      mail: email,
      pass: password,
    };
    await fetch('https://www.example.com/user/login?_format=json', {
      method: 'POST',
      headers: new Headers({
        Accept: 'application/json',
      });
      body: JSON.stringify(loginPost),
      credentials: 'same-origin',
    })
      .then(handleErrors)
      .then((response) => response.json())
      .then((data) => {
        const expireLate = 'Thu Nov 26 2049 15:44:38';
        document.cookie = 'myJsonObject=' + JSON.stringify(data) + ';expires=' + expireLate;
      })
      .catch((error) => Promise.reject(new Error(error)));
  }

Here's the content of the fetched cookie as shown in Chrome Dev Tools:

Name: myJsonObject

Value:

{"current_user":{"uid":"152","mail":"[email protected]"},"csrf_token":"bjaJpsNSEutjQW1BKZXxZbwaofuygF8M8vUitVvAJGo","logout_token":"27poFxhzClLA5WmYbDgql9n5v82Vc6CZBDNEztmBvb0"}

This is completely different from the session cookie I expected and that is retrieved by curl. How do I get the Drupal session cookie in a way that I can log in to my site when it is opened up in a browser from the app?

When I enter the header and request info in Postman, I get the json cookie returned by fetch api as the output, but I also get the session cookie automatically somehow. This automatic saving of the Drupal session cookie is what I want in my Ionic login form, but I don't understand enough about how Drupal handles cookies to determine how to save/retrieve the session cookie.

Background

I understand that the best practice is "use OAuth 2 or JWT; they are more secure."

In fact, the Simple Oauth module has good documentation and I managed to set this up on my site using the Drupalize.me react tutorial.

4
  • Can you not just recreate that curl request with a js post request? I haven't tried it, but it looks like it would be straightforward with jQuery.ajax().
    – sonfd
    Feb 18, 2021 at 12:53
  • Please keep in mind the session cookie has the httpOnly flag set, so it's not available from client-side javascript Feb 19, 2021 at 10:10
  • @DavidThomas Ok, I'm a little clueless. So you're saying I will need to log in with OAuth in the app, and then to be logged in on my Drupal site when the ionic app opens a webview, I also need to get the session cookie? Feb 19, 2021 at 10:52
  • 1
    @PatrickKenny not necessarily, but if you want to use the session cookie, you'll need to consider the httpOnly flag and also same-origin policy for the domain. If the app is a subdomain, you can share the cookie across those domains automatically. Also possible to add the session_id to /user/login response or similar, for client-side reference if preferred. Feb 22, 2021 at 9:21

1 Answer 1

0

Here's one way to do it, but the conditions are pretty specific:

  1. The app is built using Ionic.
  2. The Ionic webapp is being hosted by the Drupal server.
  3. The session cookie is only needed in the webapp (this will not work for mobile Ionic apps).

In my case, this was fine, because I wanted to redirect to my Commerce store on the webapp, but the mobile apps use IAP so they don't need that.

Here's the code:

  async function login(email: string, password: string): Promise<void> {
    const loginPost = {
      mail: email,
      pass: password,
    };
    const formData = new FormData();
    formData.append('grant_type', 'email_registration');
    formData.append('client_id', updatedConfig.client_id);
    formData.append('client_secret', updatedConfig.client_secret);
    formData.append('scope', updatedConfig.scope);
    formData.append('mail', email);
    formData.append('password', password);
    await fetch(`${baseUrl}/oauth/token`, {
      method: 'POST',
      headers: basicHeaders,
      body: formData,
    })
      .then(handleErrors)
      .then((response) => response.json())
      .then((data) => saveToken(data))
      .catch((error) => Promise.reject(new Error(error)));
    // If we are in the web browser, make an additional log in request to get the session cookie.
    if (isPlatform('desktop') || (isPlatform('mobileweb'))) {
      await fetch(`${baseUrl}/user/login?_format=json`, {
        method: 'POST',
        headers: basicHeaders,
        body: JSON.stringify(loginPost),
        credentials: 'same-origin',
      });
    }
  }

Basically, it works like this:

  1. Log in with Oauth2.
  2. Check the platform (Ionic function). If we are using the webapp, post a login request to Drupal, which will return the session cookie.

Then you need to make sure you delete the session cookie on logout:

  function logout(): Promise<boolean> {
    localStorage.removeItem(updatedConfig.token_name);
    if (isPlatform('desktop') || (isPlatform('mobileweb'))) {
      fetch(`${baseUrl}/user/logout?_format=json`, {
        method: 'GET',
        headers: basicHeaders,
        credentials: 'same-origin',
      });
    }
    return Promise.resolve(true);
  }

I'm still working on improving this, but it's a starting point at least.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.