Skip to content

Instantly share code, notes, and snippets.

@blashbrook
Created October 30, 2025 20:56
Show Gist options
  • Select an option

  • Save blashbrook/707e16012f577d3598e66d14eb8079eb to your computer and use it in GitHub Desktop.

Select an option

Save blashbrook/707e16012f577d3598e66d14eb8079eb to your computer and use it in GitHub Desktop.
Install EntraSSO Package into existing Laravel Application
#!/bin/bash
# Laravel Entra SSO Setup Script
# This script sets up an existing Laravel app to use the Entra SSO package
set -e
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
echo -e "${BLUE}β•‘ Laravel Entra SSO Setup β•‘${NC}"
echo -e "${BLUE}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}"
echo ""
# Check if we're in a Laravel directory
if [ ! -f "artisan" ]; then
echo -e "${RED}Error: Not in a Laravel directory!${NC}"
echo "Please run this script from your Laravel project root."
exit 1
fi
echo -e "${GREEN}βœ“ Laravel project detected${NC}"
echo ""
# Prompt for package location
read -p "Enter path to entra-sso package (default: ../entra-sso): " package_path
package_path=${package_path:-"../entra-sso"}
if [ ! -d "$package_path" ]; then
echo -e "${RED}Error: Package directory not found at ${package_path}${NC}"
exit 1
fi
echo -e "${GREEN}βœ“ Package found${NC}"
echo ""
# Get vendor name from package composer.json
vendor_name=$(grep -o '"name": "[^"]*"' ${package_path}/composer.json | head -1 | cut -d'"' -f4)
echo -e "${BLUE}Step 1: Adding package to composer.json${NC}"
# Ensure minimum-stability is set to dev
if grep -q "\"minimum-stability\"" composer.json; then
if [ "$(uname)" = "Darwin" ]; then
sed -i '' 's/"minimum-stability": "[^"]*"/"minimum-stability": "dev"/' composer.json
else
sed -i 's/"minimum-stability": "[^"]*"/"minimum-stability": "dev"/' composer.json
fi
echo -e "${GREEN}βœ“ Minimum stability updated to dev${NC}"
else
if [ "$(uname)" = "Darwin" ]; then
sed -i '' '/"name":/a\
"minimum-stability": "dev",
' composer.json
else
sed -i '/"name":/a\ "minimum-stability": "dev",' composer.json
fi
echo -e "${GREEN}βœ“ Minimum stability set to dev${NC}"
fi
# Check if repository already exists
if grep -q "\"type\": \"path\"" composer.json 2>/dev/null; then
echo -e "${YELLOW}Repository already exists in composer.json${NC}"
else
# Add repository to composer.json
if [ "$(uname)" == "Darwin" ]; then
# macOS
sed -i '' '/"require": {/i\
"repositories": [\
{\
"type": "path",\
"url": "'${package_path}'"\
}\
],
' composer.json
else
# Linux
sed -i '/"require": {/i\ "repositories": [\n {\n "type": "path",\n "url": "'${package_path}'"\n }\n ],' composer.json
fi
echo -e "${GREEN}βœ“ Repository added${NC}"
fi
# Add package to require section
if grep -q "${vendor_name}" composer.json; then
echo -e "${YELLOW}Package already in composer.json${NC}"
else
# Add to require
if [ "$(uname)" == "Darwin" ]; then
sed -i '' 's|\"require\": {|\"require\": {\
\"'${vendor_name}'\": \"*\",|' composer.json
else
sed -i 's|\"require\": {|\"require\": {\\n \"'${vendor_name}'\": \"*\",|' composer.json
fi
echo -e "${GREEN}βœ“ Package added to require${NC}"
fi
echo ""
echo -e "${BLUE}Step 2: Installing package${NC}"
composer update ${vendor_name}
echo -e "${GREEN}βœ“ Package installed${NC}"
echo ""
echo -e "${BLUE}Step 3: Publishing configuration${NC}"
php artisan vendor:publish --tag=entra-sso-config
echo -e "${GREEN}βœ“ Configuration published${NC}"
echo ""
echo -e "${BLUE}Step 4: Copying migration file${NC}"
cp ${package_path}/database/migrations/*_add_entra_fields_to_users.php database/migrations/
echo -e "${GREEN}βœ“ Migration file copied${NC}"
echo ""
echo -e "${BLUE}Step 5: Updating User model${NC}"
# Backup User model
cp app/Models/User.php app/Models/User.php.backup
# Check if already using EntraUser
if grep -q "use Dcplibrary\\\\EntraSSO\\\\Models\\\\User" app/Models/User.php; then
echo -e "${YELLOW}User model already extends EntraUser${NC}"
else
# Replace the Authenticatable import
if [ "$(uname)" == "Darwin" ]; then
sed -i '' "s/use Illuminate\\\\Foundation\\\\Auth\\\\User as Authenticatable;/use Dcplibrary\\\\EntraSSO\\\\Models\\\\User as EntraUser;/" app/Models/User.php
else
sed -i "s/use Illuminate\\\\Foundation\\\\Auth\\\\User as Authenticatable;/use Dcplibrary\\\\EntraSSO\\\\Models\\\\User as EntraUser;/" app/Models/User.php
fi
# Replace the class extension
if [ "$(uname)" == "Darwin" ]; then
sed -i '' "s/class User extends Authenticatable/class User extends EntraUser/" app/Models/User.php
else
sed -i "s/class User extends Authenticatable/class User extends EntraUser/" app/Models/User.php
fi
echo -e "${GREEN}βœ“ User model updated to extend EntraUser${NC}"
echo -e "${YELLOW} Note: The EntraUser base class provides:${NC}"
echo -e "${YELLOW} - entra_id, role, entra_groups, entra_custom_claims (fillable)${NC}"
echo -e "${YELLOW} - Array casts for entra_groups and entra_custom_claims${NC}"
echo -e "${YELLOW} - Helper methods: hasRole(), inGroup(), isAdmin(), etc.${NC}"
echo -e "${YELLOW} (Backup saved as app/Models/User.php.backup)${NC}"
fi
echo ""
echo -e "${BLUE}Step 6: Creating views${NC}"
# Create auth directory if it doesn't exist
mkdir -p resources/views/auth
# Create login view
if [ -f "resources/views/auth/login.blade.php" ]; then
echo -e "${YELLOW}Login view already exists, creating login-sso.blade.php${NC}"
view_file="resources/views/auth/login-sso.blade.php"
else
view_file="resources/views/auth/login.blade.php"
fi
cat > ${view_file} << 'LOGIN_EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - {{ config('app.name') }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 48px;
max-width: 400px;
width: 100%;
}
.logo {
text-align: center;
margin-bottom: 32px;
}
.logo h1 {
font-size: 32px;
font-weight: 700;
color: #1a202c;
margin-bottom: 8px;
}
.logo p {
color: #718096;
font-size: 14px;
}
.sso-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 14px 24px;
background: #0078d4;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
font-size: 16px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.sso-button:hover {
background: #005a9e;
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 120, 212, 0.3);
}
.sso-button svg {
margin-right: 12px;
}
.error {
background: #fee;
border: 1px solid #fcc;
color: #c33;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
}
.divider {
display: flex;
align-items: center;
text-align: center;
margin: 24px 0;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #e2e8f0;
}
.divider span {
padding: 0 16px;
color: #718096;
font-size: 14px;
}
.footer {
text-align: center;
margin-top: 24px;
color: #718096;
font-size: 12px;
}
</style>
</head>
<body>
<div class="login-container">
<div class="logo">
<h1>{{ config('app.name') }}</h1>
<p>Sign in to continue</p>
</div>
@if($errors->any())
<div class="error">
{{ $errors->first() }}
</div>
@endif
<a href="{{ route('entra.login') }}" class="sso-button">
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="10" height="10" fill="currentColor"/>
<rect x="11" width="10" height="10" fill="currentColor" opacity="0.8"/>
<rect y="11" width="10" height="10" fill="currentColor" opacity="0.8"/>
<rect x="11" y="11" width="10" height="10" fill="currentColor" opacity="0.6"/>
</svg>
Sign in with Microsoft
</a>
<div class="footer">
Secured by Azure Active Directory
</div>
</div>
</body>
</html>
LOGIN_EOF
echo -e "${GREEN}βœ“ Login view created: ${view_file}${NC}"
# Create dashboard view
cat > resources/views/dashboard.blade.php << 'DASHBOARD_EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - {{ config('app.name') }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f7fafc;
color: #2d3748;
}
.navbar {
background: white;
border-bottom: 1px solid #e2e8f0;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar h1 {
font-size: 24px;
font-weight: 700;
}
.logout-btn {
background: #e53e3e;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: #c53030;
}
.container {
max-width: 1200px;
margin: 40px auto;
padding: 0 24px;
}
.welcome-card {
background: white;
border-radius: 12px;
padding: 32px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
}
.welcome-card h2 {
font-size: 28px;
margin-bottom: 16px;
color: #1a202c;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-top: 24px;
}
.info-card {
background: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
}
.info-card h3 {
font-size: 14px;
color: #718096;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
.info-card p {
font-size: 18px;
font-weight: 600;
color: #2d3748;
}
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 14px;
font-weight: 600;
margin: 4px;
}
.badge-admin {
background: #fef5e7;
color: #d68910;
}
.badge-user {
background: #e8f5e9;
color: #388e3c;
}
.badge-group {
background: #e3f2fd;
color: #1976d2;
}
ul {
list-style: none;
margin-top: 12px;
}
ul li {
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
}
</style>
</head>
<body>
<nav class="navbar">
<h1>{{ config('app.name') }}</h1>
<form method="POST" action="{{ route('entra.logout') }}">
@csrf
<button type="submit" class="logout-btn">Logout</button>
</form>
</nav>
<div class="container">
<div class="welcome-card">
<h2>Welcome, {{ auth()->user()->name }}! πŸ‘‹</h2>
<p style="color: #718096; margin-top: 8px;">You're successfully logged in via Microsoft Entra SSO</p>
</div>
<div class="info-grid">
<div class="info-card">
<h3>Email</h3>
<p>{{ auth()->user()->email }}</p>
</div>
<div class="info-card">
<h3>Role</h3>
<p>
<span class="badge {{ auth()->user()->role === 'admin' ? 'badge-admin' : 'badge-user' }}">
{{ ucfirst(auth()->user()->role) }}
</span>
</p>
</div>
@if(auth()->user()->entra_id)
<div class="info-card">
<h3>Entra ID</h3>
<p style="font-size: 14px; word-break: break-all;">{{ auth()->user()->entra_id }}</p>
</div>
@endif
</div>
@if(auth()->user()->entra_groups && count(auth()->user()->entra_groups) > 0)
<div class="welcome-card" style="margin-top: 24px;">
<h3 style="margin-bottom: 16px;">Your Azure AD Groups</h3>
<div>
@foreach(auth()->user()->entra_groups as $group)
<span class="badge badge-group">{{ $group }}</span>
@endforeach
</div>
</div>
@endif
@if(auth()->user()->entra_custom_claims && count(auth()->user()->entra_custom_claims) > 0)
<div class="welcome-card" style="margin-top: 24px;">
<h3 style="margin-bottom: 16px;">Custom Claims</h3>
<ul>
@foreach(auth()->user()->entra_custom_claims as $key => $value)
<li>
<strong>{{ $key }}:</strong> {{ is_array($value) ? json_encode($value) : $value }}
</li>
@endforeach
</ul>
</div>
@endif
</div>
</body>
</html>
DASHBOARD_EOF
echo -e "${GREEN}βœ“ Dashboard view created${NC}"
echo ""
echo -e "${BLUE}Step 7: Updating routes${NC}"
# Add routes if not already present
if grep -q "route('entra.login')" routes/web.php 2>/dev/null; then
echo -e "${YELLOW}Routes already configured${NC}"
else
cat >> routes/web.php << 'ROUTES_EOF'
// SSO Routes (provided by package automatically)
// Login page
Route::get('/login', function () {
return view('auth.login');
})->name('login');
// Dashboard (protected)
Route::middleware('auth')->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
ROUTES_EOF
echo -e "${GREEN}βœ“ Routes added${NC}"
fi
echo ""
echo -e "${BLUE}Step 8: Environment configuration${NC}"
# Prompt for Azure credentials
echo ""
echo -e "${YELLOW}Please enter your Azure AD credentials:${NC}"
read -p "Tenant ID: " tenant_id
read -p "Client ID: " client_id
read -p "Client Secret: " client_secret
# Add to .env if not already present
if grep -q "ENTRA_TENANT_ID" .env; then
echo -e "${YELLOW}Entra configuration already in .env${NC}"
echo -e "${YELLOW}Please update manually if needed${NC}"
else
cat >> .env << ENV_EOF
# Entra SSO Configuration
ENTRA_TENANT_ID=${tenant_id}
ENTRA_CLIENT_ID=${client_id}
ENTRA_CLIENT_SECRET=${client_secret}
ENTRA_REDIRECT_URI="\${APP_URL}/auth/entra/callback"
ENTRA_AUTO_CREATE_USERS=true
ENTRA_SYNC_GROUPS=true
ENTRA_SYNC_ON_LOGIN=true
ENTRA_DEFAULT_ROLE=user
ENTRA_ENABLE_TOKEN_REFRESH=true
ENTRA_REFRESH_THRESHOLD=5
ENTRA_STORE_CUSTOM_CLAIMS=false
ENV_EOF
echo -e "${GREEN}βœ“ Environment variables added${NC}"
fi
echo ""
echo -e "${BLUE}Step 9: Running migrations${NC}"
read -p "Run migrations now? (y/n): " run_migrations
if [ "$run_migrations" = "y" ] || [ "$run_migrations" = "Y" ]; then
php artisan migrate
echo -e "${GREEN}βœ“ Migrations completed${NC}"
else
echo -e "${YELLOW}⚠ Remember to run: php artisan migrate${NC}"
fi
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN}βœ“ Setup Complete!${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Next Steps:${NC}"
echo " 1. Configure Azure AD application (if not done)"
echo " 2. Update role mappings in config/entra-sso.php"
echo " 3. Start Laravel: ${YELLOW}php artisan serve${NC}"
echo " 4. Visit: ${YELLOW}http://localhost:8000/login${NC}"
echo " 5. Click 'Sign in with Microsoft'"
echo ""
echo -e "${BLUE}Documentation:${NC}"
echo " - Installation: ${package_path}/docs/INSTALLATION.md"
echo " - Azure Setup: ${package_path}/docs/AZURE_SETUP.md"
echo " - Custom Claims: ${package_path}/docs/CUSTOM_CLAIMS.md"
echo ""
echo -e "${GREEN}Happy coding! πŸš€${NC}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment