Monday, July 13, 2026

Sending email from website using Microsoft Office 365 OAuth2


Step 1: Register an Application in Microsoft Entra ID

  1. Sign in to the Microsoft Azure portal:

    Microsoft Azure Portal

  2. Navigate to:

    Microsoft Entra ID → App registrations → New registration

  3. Configure:

    • Name: My Email Application

    • Supported account types:

      • Usually Accounts in this organizational directory only

    • Redirect URI:

      • Web: http://localhost:8080/callback

      • (Can be changed later)

  4. Click Register.


Step 2: Create a Client Secret

  1. Open your app registration.

  2. Go to:
    Certificates & Secrets → New client secret

  3. Enter a description.

  4. Choose expiry period.

  5. Click Add.

  6. Copy and save:

    • Client Secret Value

    • Application (Client) ID

    • Directory (Tenant) ID


Step 3: Add API Permissions

Go to:

API Permissions → Add Permission → Microsoft Graph

Add delegated permissions:

  • offline_access

  • openid

  • profile

  • Mail.Send

Click Grant Admin Consent if required.


Step 4: Generate Authorization URL

Replace placeholders:

https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize
?client_id=CLIENT_ID
&response_type=code
&redirect_uri=http://localhost:8080/callback
&response_mode=query
&scope=offline_access Mail.Send

Example:

https://login.microsoftonline.com/xxxxxxxx/oauth2/v2.0/authorize?client_id=yyyyyyyy&response_type=code&redirect_uri=http://localhost:8080/callback&response_mode=query&scope=offline_access%20Mail.Send

Open this URL in a browser.


Step 5: Sign In and Grant Consent

  1. Log in with the Office 365 mailbox account.

  2. Accept permissions.

  3. Microsoft redirects to:

http://localhost:8080/callback?code=LONG_AUTH_CODE

Copy the value of code.


Step 6: Exchange Authorization Code for Refresh Token

Using cURL:

curl -X POST \
https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=http://localhost:8080/callback"

Response:

{
  "token_type": "Bearer",
  "expires_in": 3599,
  "access_token": "...",
  "refresh_token": "0.AAAA...."
}

Copy the value of:

refresh_token

Step 7: Configure Your Application

For a CodeIgniter 4 application, place it in .env:

office365.clientId=YOUR_CLIENT_ID
office365.clientSecret=YOUR_CLIENT_SECRET
office365.tenantId=YOUR_TENANT_ID
office365.refreshToken=YOUR_REFRESH_TOKEN

Or in your custom configuration file:

public string $clientId = '...';
public string $clientSecret = '...';
public string $tenantId = '...';
public string $refreshToken = '...';

Step 8: Test Token Refresh

Test manually:

curl -X POST \
https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET" \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN"

If successful, Microsoft returns a new access token.



Sunday, July 12, 2026

Vue Codeigniter Integration

Let CodeIgniter serve the initial HTML page, and let Vue mount into a <div id="app"></div>. This gives you the benefits of CodeIgniter routing, sessions (Shield), CSRF, SEO-friendly pages, and future API/mobile support.

Recommended Project Structure

project-root/
├── app/
│   ├── Controllers/
│   │   └── Home.php
│   └── Views/
│       └── welcome_message.php
│
├── public/
│   ├── build/
│   │   ├── assets/
│   │   └── .vite/
│   ├── index.php
│   └── favicon.ico
│
├── frontend/
│   ├── src/
│   │   ├── main.js
│   │   ├── App.vue
│   │   └── router/
│   ├── index.html
│   └── vite.config.js

1. Build Vue

npm run build

This generates

public/build/
    assets/
        index-xxxxx.js
        index-xxxxx.css

2. Create the CodeIgniter View

app/Views/welcome_message.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <?= vite('frontend') ?>
</head>
<body>

<div id="app"></div>

</body>
</html>

3. Home Controller

<?php

namespace App\Controllers;

class Home extends BaseController
{
    public function index()
    {
        return view('welcome_message');
    }
}

4. Routes

$routes->get('/', 'Home::index');

Now visiting

http://localhost/

loads the CI page.


5. Vue Entry

src/main.js

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Vue will mount inside

<div id="app"></div>

6. During Development (Vite)

Instead of loading the built assets, load the Vite development server.

Create a helper:

<?php

function vite(string $entry = 'frontend')
{
    if (ENVIRONMENT === 'development')
    {
        return '
            <script type="module" src="http://localhost:5173/@vite/client"></script>
            <script type="module" src="http://localhost:5173/src/main.js"></script>
        ';
    }

    $manifest = json_decode(
        file_get_contents(FCPATH.'build/.vite/manifest.json'),
        true
    );

    $file = $manifest['src/main.js'];

    $html = '';

    if (isset($file['css'])) {
        foreach ($file['css'] as $css) {
            $html .= '<link rel="stylesheet" href="'.base_url('build/'.$css).'">';
        }
    }

    $html .= '<script type="module" src="'.base_url('build/'.$file['file']).'"></script>';

    return $html;
}

Then

<?= vite() ?>

works in both development and production.


7. Vue Router

Configure history mode.

import { createRouter, createWebHistory } from 'vue-router'

export default createRouter({
    history: createWebHistory(),
    routes: [
        // routes
    ]
})

Register it:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
    .use(router)
    .mount('#app')

8. Catch All Routes in CodeIgniter

If Vue Router handles navigation, make CodeIgniter always return the same view:

$routes->get('/', 'Home::index');

$routes->get('(:any)', 'Home::index');

or

$routes->add('(:any)', 'Home::index');

This allows routes like

/
/about
/products
/dashboard

to all load the Vue application.


9. CodeIgniter + Shield Authentication

Since CodeIgniter serves the page:

  • Shield sessions work normally.

  • CSRF protection remains available.

  • You can expose authenticated user data to Vue before it mounts if needed:

<script>
window.App = {
    user: <?= json_encode(auth()->user()) ?>
};
</script>

Then in Vue:

const user = window.App.user;

Architecture Recommendation

For CodeIgniter 4 + Vue + TailwindCSS + CodeIgniter Shield application (with future mobile API support), a clean architecture is:

Browser
   │
   ▼
CodeIgniter Route
   │
   ▼
Home Controller
   │
   ▼
welcome_message.php
   │
   ├── <div id="app"></div>
   ├── Inject authenticated user/session data
   └── Load Vite-built JS/CSS
            │
            ▼
         Vue App
            │
      Vue Router
            │
      Axios/Fetch API
            │
            ▼
CodeIgniter REST API
            │
      Shield Authentication
            │
        Database

This keeps CodeIgniter responsible for the initial request, authentication, and APIs, while Vue handles the entire client-side UI, which is scalable and aligns well with a future mobile application consuming the same REST APIs.

Sending email from website using Microsoft Office 365 OAuth2

Step 1: Register an Application in Microsoft Entra ID Sign in to the Microsoft Azure portal: Microsoft Azure Portal Navigate to: Microsoft E...