• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NIT-Administrative-Systems / SysDev-laravel-soa / 24248397682

10 Apr 2026 02:38PM UTC coverage: 42.581% (+0.2%) from 42.393%
24248397682

Pull #194

github

web-flow
Merge 8da8371a1 into e62a38c19
Pull Request #194: feat: Laravel 13 compatibility

6 of 9 new or added lines in 1 file covered. (66.67%)

330 of 775 relevant lines covered (42.58%)

13.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.15
/src/Auth/WebSSOAuthentication.php
1
<?php
2

3
namespace Northwestern\SysDev\SOA\Auth;
4

5
use GuzzleHttp\Exception\ClientException;
6
use Illuminate\Auth\AuthenticationException;
7
use Illuminate\Contracts\Auth\Authenticatable;
8
use Illuminate\Foundation\Auth\RedirectsUsers;
9
use Illuminate\Http\Request;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Facades\Auth;
12
use Illuminate\Support\Facades\Session;
13
use Illuminate\Support\Str;
14
use Laravel\Socialite\Facades\Socialite;
15
use Laravel\Socialite\Two\InvalidStateException;
16
use Northwestern\SysDev\SOA\Auth\Entity\ActiveDirectoryUser;
17
use Northwestern\SysDev\SOA\Auth\Entity\OAuthUser;
18
use Northwestern\SysDev\SOA\Auth\OAuth2\NorthwesternAzureProvider;
19
use Northwestern\SysDev\SOA\Auth\Strategy\NoSsoSession;
20
use Northwestern\SysDev\SOA\Auth\Strategy\WebSSOStrategy;
21

22
/**
23
 * @phpstan-ignore trait.unused
24
 */
25
trait WebSSOAuthentication
26
{
27
    use RedirectsUsers, WebSSORoutes;
28

29
    /**
30
     * OpenAM WebSSO login action.
31
     */
32
    public function login(Request $request, WebSSOStrategy $sso_strategy)
33
    {
34
        try {
35
            $netid = strtolower($sso_strategy->login($request, $this->login_route_name));
18✔
36
        } catch (NoSsoSession $e) {
15✔
37
            return redirect($e->getRedirectUrl());
9✔
38
        }
39

40
        $user = app()->call(static::findUserByNetID(...), ['netid' => $netid]);
3✔
41
        throw_if($user === null, new AuthenticationException());
3✔
42

43
        Auth::login($user);
3✔
44
        Session::regenerate();
3✔
45

46
        return $this->authenticated($request, $user) ?: redirect()->intended($this->redirectPath());
3✔
47
    }
48

49
    /**
50
     * OpenAM WebSSO logout action.
51
     */
52
    public function logout(WebSSOStrategy $sso_strategy)
53
    {
54
        Auth::logout();
×
NEW
55
        Session::invalidate();
×
NEW
56
        Session::regenerateToken();
×
57

58
        return $sso_strategy->logout($this->logout_return_to_route);
×
59
    }
60

61
    public function oauthLogout($postLogoutRedirectUri = null)
62
    {
63
        Auth::logout();
6✔
64
        Session::invalidate();
6✔
65
        Session::regenerateToken();
6✔
66

67
        if ($postLogoutRedirectUri != null) {
6✔
68
            $url = $this->oauthDriver()->getLogoutUrl().'?post_logout_redirect_uri='.urlencode($postLogoutRedirectUri);
3✔
69

70
            return redirect($url);
3✔
71
        }
72

73
        return redirect($this->oauthDriver()->getLogoutUrl());
3✔
74
    }
75

76
    /**
77
     * Entra ID OAuth initiator action.
78
     */
79
    public function oauthRedirect()
80
    {
81
        return $this->oauthDriver()->redirect();
3✔
82
    }
83

84
    /**
85
     * OAuth callback URL, where users are sent after authenticating with Entra ID.
86
     */
87
    public function oauthCallback(Request $request)
88
    {
89
        try {
90
            $userInfo = $this->oauthDriver()->user();
12✔
91
        } catch (InvalidStateException $e) {
9✔
92
            // Should be resolvable by starting the flow over
93
            return redirect(route($this->oauth_redirect_route_name));
3✔
94
        } catch (ClientException $e) {
6✔
95
            /**
96
             * Handle specific failures that we know can be resolved by re-starting the auth flow.
97
             * Anything more general from Guzzle should rethrow and be handled
98
             * by the app's exception handler.
99
             */
100
            if (
101
                $e->getCode() === 400
3✔
102
                && Str::contains($e->getMessage(), 'OAuth2 Authorization code was already redeemed')
3✔
103
            ) {
104
                return redirect(route($this->oauth_redirect_route_name));
3✔
105
            }
106

107
            throw $e;
×
108
        }
109

110
        $oauthUser = new ActiveDirectoryUser(
3✔
111
            $userInfo->token,
3✔
112
            $userInfo->getRaw(),
3✔
113
            Arr::get($userInfo->attributes, NorthwesternAzureProvider::ISSUER_ATTRIBUTE)
3✔
114
        );
3✔
115

116
        $user = app()->call(
3✔
117
            static::findUserByOAuthUser(...),
3✔
118
            ['oauthUser' => $oauthUser]
3✔
119
        );
3✔
120
        throw_if($user === null, new AuthenticationException());
3✔
121

122
        Auth::login($user);
3✔
123
        Session::regenerate();
3✔
124

125
        return $this->authenticated($request, $user) ?: redirect()->intended($this->redirectPath());
3✔
126
    }
127

128
    /**
129
     * Retrieve a user model for a given OAuth profile.
130
     *
131
     * By default, this method will pass the netID through to ::findUserByNetId instead
132
     * of doing anything on its own. This is for backwards-compatibility -- if you've used
133
     * OpenAM SSO in the past (or plan to in the future), the two methods can be used interchangably.
134
     *
135
     * In cases where you wish to utilize data from the Entra ID profile (like email, name, phone, etc),
136
     * you can implement this method and return a Laravel user directly, without invoking the
137
     * ::findUserByNetID method.
138
     */
139
    protected function findUserByOAuthUser(OAuthUser $oauthUser): ?Authenticatable
140
    {
141
        return app()->call(
×
NEW
142
            static::findUserByNetID(...),
×
143
            ['netid' => $oauthUser->getNetid()]
×
144
        );
×
145
    }
146

147
    /**
148
     * Retrieve a user model for a given netID.
149
     *
150
     * This is an opportunity to create a user in your DB, if needed.
151
     *
152
     * If you do not have a user store, a plain-old PHP object implementing
153
     * the Illuminate\Contracts\Auth\Authenticatable interface is sufficient.
154
     */
155
    protected function findUserByNetID(string $netid): ?Authenticatable
156
    {
157
        throw new \Exception('findUserByNetID is not implemented, but must be implemented.');
×
158
    }
159

160
    /**
161
     * Post-authentication hook.
162
     *
163
     * You may return a response here, e.g. a redirect() somewhere,
164
     * and it will be respected.
165
     */
166
    protected function authenticated(Request $request, $user)
167
    {
168
        //
169
    }
6✔
170

171
    /**
172
     * @return \Laravel\Socialite\Contracts\Provider
173
     */
174
    protected function oauthDriver()
175
    {
176
        $driver = Socialite::driver('northwestern-azure')->scopes($this->oauthScopes());
×
177

178
        if (! config('services.azure.redirect')) {
×
179
            $driver = $driver->redirectUrl(route($this->oauth_callback_route_name, [], true));
×
180
        }
181

182
        return $driver;
×
183
    }
184

185
    /**
186
     * Additional scopes for the user.
187
     *
188
     * @return array
189
     */
190
    protected function oauthScopes()
191
    {
192
        return ['https://graph.microsoft.com/User.Read'];
×
193
    }
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc