1 | # angular-oauth2-oidc
|
2 |
|
3 | Support for OAuth 2 and OpenId Connect (OIDC) in Angular. Already prepared for the upcoming OAuth 2.1.
|
4 |
|
5 | 
|
6 |
|
7 | ## Credits
|
8 |
|
9 | - [jsrsasign](https://kjur.github.io/jsrsasign/) for validating token signature and for hashing
|
10 | - [Identity Server](https://github.com/identityserver) for testing with an .NET/.NET Core Backend
|
11 | - [Keycloak (Redhat)](http://www.keycloak.org/) for testing with Java
|
12 | - [Auth0](https://auth0.com/)
|
13 |
|
14 | ## Resources
|
15 |
|
16 | - Sources and Sample: [https://github.com/manfredsteyer/angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc)
|
17 | - Source Code Documentation: [https://manfredsteyer.github.io/angular-oauth2-oidc/docs](https://manfredsteyer.github.io/angular-oauth2-oidc/docs)
|
18 | - Community-provided sample implementation: [https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/](https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/)
|
19 |
|
20 |
|
21 | ## Tested Environment
|
22 |
|
23 | Successfully tested with **Angular 4.3 to Angular 16** and its Router, PathLocationStrategy as well as HashLocationStrategy and CommonJS-Bundling via webpack.
|
24 |
|
25 | At server side we've used **IdentityServer** (.NET / .NET Core), Redhat's **Keycloak** (Java), and **Auth0** (Auth0 is officially supported since version 10 of this lib). For Auth0, please have a look into the respective documentation page here.
|
26 |
|
27 | For using this library with **Azure Active Directory** (**Azure AD**), we recommend an additional look to this [blog post](https://dev.to/yuriburger/azure-active-directory-b2c-with-pkce-for-your-angular-app-1dcg) and the example linked at the end of this blog post.
|
28 |
|
29 | Also, the Okta community created some guidelines on how to use this lib with Okta. See the links at the end of this page for more information.
|
30 |
|
31 | **Angular 17**: Use 17.x versions of this library (**should also work with older Angular versions!**).
|
32 |
|
33 | **Angular 16**: Use 16.x versions of this library (**should also work with older Angular versions!**).
|
34 |
|
35 | **Angular 15**: Use 15.x versions of this library (**should also work with older Angular versions!**).
|
36 |
|
37 | **Angular 14**: Use 14.x versions of this library (**should also work with older Angular versions!**).
|
38 |
|
39 | **Angular 13**: Use 13.x versions of this library (**should also work with older Angular versions!**).
|
40 |
|
41 | **Angular 12**: Use 12.x versions of this library (**should also work with older Angular versions!**).
|
42 |
|
43 | **Angular 11**: Use 10.x versions of this library (**should also work with older Angular versions!**).
|
44 |
|
45 | **Angular 10**: Use 10.x versions of this library (**should also work with older Angular versions!**).
|
46 |
|
47 | **Angular 9**: Use 9.x versions of this library (**should also work with older Angular versions!**).
|
48 |
|
49 | **Angular 8**: Use 8.x versions of this library.
|
50 |
|
51 | **Angular 7**: Use 7.x versions of this library.
|
52 |
|
53 | **Angular 6**: Use Version 4.x of this library. Version 4.x was tested with Angular 6. You can also try the newer version 5.x of this library which has a much smaller bundle size.
|
54 |
|
55 | **Angular 5.x or 4.3**: If you need support for Angular < 6 (4.3 to 5.x) you can download the former version 3.1.4 (npm i angular-oauth2-oidc@^3 --save).
|
56 |
|
57 | ## Release Cycle
|
58 |
|
59 | - We plan one major release for each Angular version
|
60 | - Will contain new features
|
61 | - Will contain bug fixes and PRs
|
62 | - Critical bugfixes on demand
|
63 |
|
64 | ## Contributions
|
65 |
|
66 | - Feel free to file pull requests
|
67 | - The issues contain some ideas for PRs and enhancements (see labels)
|
68 | - If you want to contribute to the docs, you can do so in the `docs-src` folder. Make sure you update `summary.json` as well. Then generate the docs with the following commands:
|
69 |
|
70 | ```sh
|
71 | npm install -g @compodoc/compodoc
|
72 | npm run docs
|
73 | ```
|
74 |
|
75 | ## Features
|
76 |
|
77 | - Logging in via Code Flow + PKCE
|
78 | - Hence, you are safe for the upcoming OAuth 2.1
|
79 | - Logging in via Implicit Flow (where a user is redirected to Identity Provider)
|
80 | - "Logging in" via Password Flow (where a user enters their password into the client)
|
81 | - Token Refresh for all supported flows
|
82 | - Automatically refreshing a token when/some time before it expires
|
83 | - Querying Userinfo Endpoint
|
84 | - Querying Discovery Document to ease configuration
|
85 | - Validating claims of the id_token regarding the specs
|
86 | - Hook for further custom validations
|
87 | - Single-Sign-Out by redirecting to the auth-server's logout-endpoint
|
88 | - Tested with all modern browsers and IE
|
89 | - Token Revocation according to [RFC 7009](https://tools.ietf.org/html/rfc7009#section-2.2)
|
90 |
|
91 | ## Sample-Auth-Server
|
92 |
|
93 | You can use the OIDC-Sample-Server used in our examples. It assumes, that your Web-App runs on http://localhost:4200
|
94 |
|
95 | Username/Password:
|
96 |
|
97 | - max/geheim
|
98 | - bob/bob
|
99 | - alice/alice
|
100 |
|
101 | _clientIds:_
|
102 |
|
103 | - spa (Code Flow + PKCE)
|
104 | - implicit (implicit flow)
|
105 |
|
106 | _redirectUris:_
|
107 |
|
108 | - localhost:[4200-4202]
|
109 | - localhost:[4200-4202]/index.html
|
110 | - localhost:[4200-4202]/silent-refresh.html
|
111 |
|
112 | ## Installing
|
113 |
|
114 | ```sh
|
115 | npm i angular-oauth2-oidc --save
|
116 | ```
|
117 |
|
118 |
|
119 | ## Option 1: Standalone APIs
|
120 |
|
121 | If you use Standalone Components introduced with Angular 14, you can use our standalone API (call to ``provideOAuthClient``) in your ``main.ts`` to setup the ``OAuthClient``:
|
122 |
|
123 | ```TypeScript
|
124 | // main.ts -- Angular 15+ version
|
125 | import { bootstrapApplication } from '@angular/platform-browser';
|
126 |
|
127 | import { provideHttpClient } from '@angular/common/http';
|
128 |
|
129 | import { AppComponent } from './app/app.component';
|
130 | import { provideOAuthClient } from 'angular-oauth2-oidc';
|
131 |
|
132 | bootstrapApplication(AppComponent, {
|
133 | providers: [
|
134 | provideHttpClient(),
|
135 | provideOAuthClient()
|
136 | ]
|
137 | });
|
138 | ```
|
139 |
|
140 | As Angular 14 does have Standalone Components but no Standalone API for its ``HttpClient``, you need to go with the traditional ``HttpClientModule`` in this version:
|
141 |
|
142 | ```TypeScript
|
143 | // main.ts -- Angular 14 version
|
144 | import { bootstrapApplication } from '@angular/platform-browser';
|
145 |
|
146 | import { HttpClientModule } from '@angular/common/http';
|
147 |
|
148 | import { AppComponent } from './app/app.component';
|
149 | import { provideOAuthClient } from 'angular-oauth2-oidc';
|
150 | import { importProvidersFrom } from '@angular/core';
|
151 |
|
152 | bootstrapApplication(AppComponent, {
|
153 | providers: [
|
154 | importProvidersFrom(HttpClientModule),
|
155 | provideOAuthClient()
|
156 | ]
|
157 | });
|
158 | ```
|
159 |
|
160 | The ``provideOAuthClient`` function takes the same parameters as the forRoot function of the OAuthModule that is still in place for the sake of compatibility with existing code bases.
|
161 |
|
162 | ## Option 2: Using NgModules
|
163 |
|
164 | ```TypeScript
|
165 | import { HttpClientModule } from '@angular/common/http';
|
166 | import { OAuthModule } from 'angular-oauth2-oidc';
|
167 | // etc.
|
168 |
|
169 | @NgModule({
|
170 | imports: [
|
171 | // etc.
|
172 | HttpClientModule,
|
173 | OAuthModule.forRoot()
|
174 | ],
|
175 | declarations: [
|
176 | AppComponent,
|
177 | HomeComponent,
|
178 | // etc.
|
179 | ],
|
180 | bootstrap: [
|
181 | AppComponent
|
182 | ]
|
183 | })
|
184 | export class AppModule {
|
185 | }
|
186 | ```
|
187 |
|
188 | # Logging in
|
189 |
|
190 | Since Version 8, this library supports code flow and [PKCE](https://tools.ietf.org/html/rfc7636) to align with the current draft of the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) document. This is also the foundation of the upcoming OAuth 2.1.
|
191 |
|
192 | To configure your solution for code flow + PKCE you have to set the `responseType` to `code`:
|
193 |
|
194 | ```TypeScript
|
195 | import { AuthConfig } from 'angular-oauth2-oidc';
|
196 |
|
197 | export const authCodeFlowConfig: AuthConfig = {
|
198 | // Url of the Identity Provider
|
199 | issuer: 'https://idsvr4.azurewebsites.net',
|
200 |
|
201 | // URL of the SPA to redirect the user to after login
|
202 | redirectUri: window.location.origin + '/index.html',
|
203 |
|
204 | // The SPA's id. The SPA is registerd with this id at the auth-server
|
205 | // clientId: 'server.code',
|
206 | clientId: 'spa',
|
207 |
|
208 | // Just needed if your auth server demands a secret. In general, this
|
209 | // is a sign that the auth server is not configured with SPAs in mind
|
210 | // and it might not enforce further best practices vital for security
|
211 | // such applications.
|
212 | // dummyClientSecret: 'secret',
|
213 |
|
214 | responseType: 'code',
|
215 |
|
216 | // set the scope for the permissions the client should request
|
217 | // The first four are defined by OIDC.
|
218 | // Important: Request offline_access to get a refresh token
|
219 | // The api scope is a usecase specific one
|
220 | scope: 'openid profile email offline_access api',
|
221 |
|
222 | showDebugInformation: true,
|
223 | };
|
224 | ```
|
225 |
|
226 | After this, you can initialize the code flow using:
|
227 |
|
228 | ```TypeScript
|
229 | this.oauthService.initCodeFlow();
|
230 | ```
|
231 |
|
232 | There is also a convenience method `initLoginFlow` which initializes either the code flow or the implicit flow depending on your configuration.
|
233 |
|
234 | ```TypeScript
|
235 | this.oauthService.initLoginFlow();
|
236 | ```
|
237 |
|
238 | Also -- as shown in the readme -- you have to execute the following code when bootstrapping to make the library to fetch the token:
|
239 |
|
240 | ```TypeScript
|
241 | this.oauthService.configure(authCodeFlowConfig);
|
242 | this.oauthService.loadDiscoveryDocumentAndTryLogin();
|
243 | ```
|
244 |
|
245 | ### Logging out
|
246 |
|
247 | The logOut method clears the used token store (by default ``sessionStorage``) and forwards the user to the auth servers logout endpoint if one was configured (manually or via the discovery document).
|
248 |
|
249 | ```typescript
|
250 | this.oauthService.logOut();
|
251 | ```
|
252 |
|
253 | If you want to revoke the existing access token and the existing refresh token before logging out, use the following method:
|
254 |
|
255 | ```typescript
|
256 | this.oauthService.revokeTokenAndLogout();
|
257 | ```
|
258 |
|
259 | ### Skipping the Login Form
|
260 |
|
261 | If you don't want to display a login form that tells the user that they are redirected to the identity server, you can use the convenience function `this.oauthService.loadDiscoveryDocumentAndLogin();` instead of `this.oauthService.loadDiscoveryDocumentAndTryLogin();` when setting up the library.
|
262 |
|
263 | This directly redirects the user to the identity server if there are no valid tokens. Ensure you have your `issuer` set to your discovery document endpoint!
|
264 |
|
265 | ### Calling a Web API with an Access Token
|
266 |
|
267 | You can automate this task by switching `sendAccessToken` on and by setting `allowedUrls` to an array with prefixes for the respective URLs. Use lower case for the prefixes.
|
268 |
|
269 | ```TypeScript
|
270 | OAuthModule.forRoot({
|
271 | resourceServer: {
|
272 | allowedUrls: ['http://www.angular.at/api'],
|
273 | sendAccessToken: true
|
274 | }
|
275 | })
|
276 | ```
|
277 |
|
278 | If you need more versatility, you can look in the [documentation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/working-with-httpinterceptors.html) how to setup a custom interceptor.
|
279 |
|
280 | ## Token Refresh
|
281 |
|
282 | See docs: https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/refreshing-a-token.html
|
283 |
|
284 | ## Routing
|
285 |
|
286 | If you use the `PathLocationStrategy` (which is on by default) and have a general catch-all-route (`path: '**'`) you should be fine. Otherwise look up the section `Routing with the HashStrategy` in the [documentation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/).
|
287 |
|
288 | ## Implicit Flow
|
289 |
|
290 | Nowadays, using code flow + PKCE -- as shown above -- is the recommended OAuth 2/OIDC flow for SPAs. To use the older implicit flow, lookup this docs: https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/using-implicit-flow.html
|
291 |
|
292 | ## More Documentation (!)
|
293 |
|
294 | See the [documentation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/) for more information about this library.
|
295 |
|
296 |
|
297 | ## Breaking Change in Version 9
|
298 |
|
299 | With regards to tree shaking, beginning with version 9, the `JwksValidationHandler` has been moved to a library of its own. If you need it for implementing **implicit flow**, please install it using npm:
|
300 |
|
301 | ```
|
302 | npm i angular-oauth2-oidc-jwks --save
|
303 | ```
|
304 |
|
305 | After that, you can import it into your application by using this:
|
306 |
|
307 | ```typescript
|
308 | import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
309 | ```
|
310 |
|
311 | instead of that:
|
312 |
|
313 | ```typescript
|
314 | import { JwksValidationHandler } from 'angular-oauth2-oidc';
|
315 | ```
|
316 |
|
317 | Please note, that this dependency is not needed for the **code flow**, which is nowadays the **recommended** flow for single page applications. This also results in smaller bundle sizes.
|
318 |
|
319 |
|
320 |
|
321 | ### Breaking change in 9.1.0
|
322 |
|
323 | The use of `encodeURIComponent` on the argument passed to `initImplicitFlow` and its Code Flow counterparts was mandatory before this version.
|
324 |
|
325 | Since that was considered a _bug_, the need to do so was removed.
|
326 | Now the reverse is true **if you're upgrading from before 9.0.0**: you need to remove any call to encode URI components in your own application, as the library will now do it for you.
|
327 |
|
328 | ## Tutorials
|
329 |
|
330 | - [Tutorial with Demo Servers available online](https://www.softwarearchitekt.at/post/2016/07/03/authentication-in-angular-2-with-oauth2-oidc-and-guards-for-the-newest-new-router-english-version.aspx)
|
331 | - [Angular Authentication with OpenID Connect and Okta in 20 Minutes](https://developer.okta.com/blog/2017/04/17/angular-authentication-with-oidc)
|
332 | - [Add Authentication to Your Angular PWA](https://developer.okta.com/blog/2017/06/13/add-authentication-angular-pwa)
|
333 | - [Build an Ionic App with User Authentication](https://developer.okta.com/blog/2017/08/22/build-an-ionic-app-with-user-authentication)
|
334 | - [On-Site Workshops](https://www.softwarearchitekt.at)
|
335 | - [Angular 6 with Auth0 using this library](https://github.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc)
|
336 |
|
337 | ## Thanks to all Contributors
|
338 |
|
339 | [<img alt="alexandis" src="https://avatars.githubusercontent.com/u/6149843?v=4&s=117" width="117">](https://github.com/alexandis)[<img alt="anbiniyar" src="https://avatars.githubusercontent.com/u/407653?v=4&s=117" width="117">](https://github.com/anbiniyar)[<img alt="anoordende" src="https://avatars.githubusercontent.com/u/11973801?v=4&s=117" width="117">](https://github.com/anoordende)[<img alt="ArsProgramma" src="https://avatars.githubusercontent.com/u/4572729?v=4&s=117" width="117">](https://github.com/ArsProgramma)[<img alt="nihanth007" src="https://avatars.githubusercontent.com/u/14851784?v=4&s=117" width="117">](https://github.com/nihanth007)
|
340 |
|
341 | [<img alt="bobvandevijver" src="https://avatars.githubusercontent.com/u/1835343?v=4&s=117" width="117">](https://github.com/bobvandevijver)[<img alt="BobCui20" src="https://avatars.githubusercontent.com/u/59807069?v=4&s=117" width="117">](https://github.com/BobCui20)[<img alt="Bottswana" src="https://avatars.githubusercontent.com/u/6907460?v=4&s=117" width="117">](https://github.com/Bottswana)[<img alt="ErazerBrecht" src="https://avatars.githubusercontent.com/u/6287467?v=4&s=117" width="117">](https://github.com/ErazerBrecht)[<img alt="Chris3773" src="https://avatars.githubusercontent.com/u/22506071?v=4&s=117" width="117">](https://github.com/Chris3773)
|
342 |
|
343 | [<img alt="ChristianMurphy" src="https://avatars.githubusercontent.com/u/3107513?v=4&s=117" width="117">](https://github.com/ChristianMurphy)[<img alt="d-moos" src="https://avatars.githubusercontent.com/u/14070689?v=4&s=117" width="117">](https://github.com/d-moos)[<img alt="enterprisebug" src="https://avatars.githubusercontent.com/u/1539741?v=4&s=117" width="117">](https://github.com/enterprisebug)[<img alt="craniodev" src="https://avatars.githubusercontent.com/u/8593824?v=4&s=117" width="117">](https://github.com/craniodev)[<img alt="FabianGosebrink" src="https://avatars.githubusercontent.com/u/11268349?v=4&s=117" width="117">](https://github.com/FabianGosebrink)
|
344 |
|
345 | [<img alt="FabienDehopre" src="https://avatars.githubusercontent.com/u/97023?v=4&s=117" width="117">](https://github.com/FabienDehopre)[<img alt="FRosner" src="https://avatars.githubusercontent.com/u/3427394?v=4&s=117" width="117">](https://github.com/FRosner)[<img alt="MisterJames" src="https://avatars.githubusercontent.com/u/1197383?v=4&s=117" width="117">](https://github.com/MisterJames)[<img alt="JessePreiner" src="https://avatars.githubusercontent.com/u/3847360?v=4&s=117" width="117">](https://github.com/JessePreiner)[<img alt="jesusbotella" src="https://avatars.githubusercontent.com/u/4319728?v=4&s=117" width="117">](https://github.com/jesusbotella)
|
346 |
|
347 | [<img alt="Jojofoulk" src="https://avatars.githubusercontent.com/u/44689065?v=4&s=117" width="117">](https://github.com/Jojofoulk)[<img alt="kristofdegrave" src="https://avatars.githubusercontent.com/u/1322395?v=4&s=117" width="117">](https://github.com/kristofdegrave)[<img alt="saxicek" src="https://avatars.githubusercontent.com/u/1708442?v=4&s=117" width="117">](https://github.com/saxicek)[<img alt="lukasmatta" src="https://avatars.githubusercontent.com/u/4323927?v=4&s=117" width="117">](https://github.com/lukasmatta)[<img alt="Maximaximum" src="https://avatars.githubusercontent.com/u/5593500?v=4&s=117" width="117">](https://github.com/Maximaximum)
|
348 |
|
349 | [<img alt="mpbalmeida" src="https://avatars.githubusercontent.com/u/516102?v=4&s=117" width="117">](https://github.com/mpbalmeida)[<img alt="mhyfritz" src="https://avatars.githubusercontent.com/u/718983?v=4&s=117" width="117">](https://github.com/mhyfritz)[<img alt="mdaehnert" src="https://avatars.githubusercontent.com/u/1017301?v=4&s=117" width="117">](https://github.com/mdaehnert)[<img alt="mcserra" src="https://avatars.githubusercontent.com/u/16702410?v=4&s=117" width="117">](https://github.com/mcserra)[<img alt="nhumblot" src="https://avatars.githubusercontent.com/u/15015617?v=4&s=117" width="117">](https://github.com/nhumblot)
|
350 |
|
351 | [<img alt="l1b3r" src="https://avatars.githubusercontent.com/u/6207227?v=4&s=117" width="117">](https://github.com/l1b3r)[<img alt="oleersoy" src="https://avatars.githubusercontent.com/u/1163873?v=4&s=117" width="117">](https://github.com/oleersoy)[<img alt="OskarsPakers" src="https://avatars.githubusercontent.com/u/3343347?v=4&s=117" width="117">](https://github.com/OskarsPakers)[<img alt="hellerbarde" src="https://avatars.githubusercontent.com/u/37417?v=4&s=117" width="117">](https://github.com/hellerbarde)[<img alt="paweldyminski" src="https://avatars.githubusercontent.com/u/33632375?v=4&s=117" width="117">](https://github.com/paweldyminski)
|
352 |
|
353 | [<img alt="bechhansen" src="https://avatars.githubusercontent.com/u/426810?v=4&s=117" width="117">](https://github.com/bechhansen)[<img alt="peterneave" src="https://avatars.githubusercontent.com/u/7982708?v=4&s=117" width="117">](https://github.com/peterneave)[<img alt="pmccloghrylaing" src="https://avatars.githubusercontent.com/u/2329335?v=4&s=117" width="117">](https://github.com/pmccloghrylaing)[<img alt="akehir" src="https://avatars.githubusercontent.com/u/1078202?v=4&s=117" width="117">](https://github.com/akehir)[<img alt="RubenVermeulen" src="https://avatars.githubusercontent.com/u/10133445?v=4&s=117" width="117">](https://github.com/RubenVermeulen)
|
354 |
|
355 | [<img alt="ryanmwright" src="https://avatars.githubusercontent.com/u/5000122?v=4&s=117" width="117">](https://github.com/ryanmwright)[<img alt="scttcper" src="https://avatars.githubusercontent.com/u/1400464?v=4&s=117" width="117">](https://github.com/scttcper)[<img alt="abshoff" src="https://avatars.githubusercontent.com/u/2471284?v=4&s=117" width="117">](https://github.com/abshoff)[<img alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus)[<img alt="srenatus" src="https://avatars.githubusercontent.com/u/870638?v=4&s=117" width="117">](https://github.com/srenatus)
|
356 |
|
357 | [<img alt="sven-codeculture" src="https://avatars.githubusercontent.com/u/3930643?v=4&s=117" width="117">](https://github.com/sven-codeculture)[<img alt="Rocket18" src="https://avatars.githubusercontent.com/u/11396142?v=4&s=117" width="117">](https://github.com/Rocket18)[<img alt="Ceteareth" src="https://avatars.githubusercontent.com/u/1556143?v=4&s=117" width="117">](https://github.com/Ceteareth)[<img alt="vadjs" src="https://avatars.githubusercontent.com/u/10026333?v=4&s=117" width="117">](https://github.com/vadjs)[<img alt="Varada-Schneider" src="https://avatars.githubusercontent.com/u/62388762?v=4&s=117" width="117">](https://github.com/Varada-Schneider)
|
358 |
|
359 | [<img alt="Gimly" src="https://avatars.githubusercontent.com/u/168669?v=4&s=117" width="117">](https://github.com/Gimly)[<img alt="akkaradej" src="https://avatars.githubusercontent.com/u/2855965?v=4&s=117" width="117">](https://github.com/akkaradej)[<img alt="coyoteecd" src="https://avatars.githubusercontent.com/u/47973420?v=4&s=117" width="117">](https://github.com/coyoteecd)[<img alt="darbio" src="https://avatars.githubusercontent.com/u/517620?v=4&s=117" width="117">](https://github.com/darbio)[<img alt="filipvh" src="https://avatars.githubusercontent.com/u/6095002?v=4&s=117" width="117">](https://github.com/filipvh)
|
360 |
|
361 | [<img alt="kyubisation" src="https://avatars.githubusercontent.com/u/594745?v=4&s=117" width="117">](https://github.com/kyubisation)[<img alt="luciimon" src="https://avatars.githubusercontent.com/u/9714755?v=4&s=117" width="117">](https://github.com/luciimon)[<img alt="mike-rivera" src="https://avatars.githubusercontent.com/u/57490323?v=4&s=117" width="117">](https://github.com/mike-rivera)[<img alt="drobert-bfm" src="https://avatars.githubusercontent.com/u/28102639?v=4&s=117" width="117">](https://github.com/drobert-bfm)[<img alt="roblabat" src="https://avatars.githubusercontent.com/u/9885738?v=4&s=117" width="117">](https://github.com/roblabat)
|
362 |
|
363 | [<img alt="wdunn001" src="https://avatars.githubusercontent.com/u/4011100?v=4&s=117" width="117">](https://github.com/wdunn001)[<img alt="adrianbenjuya" src="https://avatars.githubusercontent.com/u/17908930?v=4&s=117" width="117">](https://github.com/adrianbenjuya)[<img alt="Andreas-Hjortland" src="https://avatars.githubusercontent.com/u/2162904?v=4&s=117" width="117">](https://github.com/Andreas-Hjortland)[<img alt="adematte" src="https://avatars.githubusercontent.com/u/5064637?v=4&s=117" width="117">](https://github.com/adematte)[<img alt="cgatian" src="https://avatars.githubusercontent.com/u/1752170?v=4&s=117" width="117">](https://github.com/cgatian)
|
364 |
|
365 | [<img alt="dirkbolte" src="https://avatars.githubusercontent.com/u/1572945?v=4&s=117" width="117">](https://github.com/dirkbolte)[<img alt="enricodeleo" src="https://avatars.githubusercontent.com/u/3534555?v=4&s=117" width="117">](https://github.com/enricodeleo)[<img alt="Gregordy" src="https://avatars.githubusercontent.com/u/10693717?v=4&s=117" width="117">](https://github.com/Gregordy)[<img alt="jeroenhinfi" src="https://avatars.githubusercontent.com/u/38323074?v=4&s=117" width="117">](https://github.com/jeroenhinfi)[<img alt="linjie997" src="https://avatars.githubusercontent.com/u/23615368?v=4&s=117" width="117">](https://github.com/linjie997)
|
366 |
|
367 | [<img alt="jfyne" src="https://avatars.githubusercontent.com/u/400281?v=4&s=117" width="117">](https://github.com/jfyne)[<img alt="kevincathcart-cas" src="https://avatars.githubusercontent.com/u/72209838?v=4&s=117" width="117">](https://github.com/kevincathcart-cas)[<img alt="martin1cerny" src="https://avatars.githubusercontent.com/u/773078?v=4&s=117" width="117">](https://github.com/martin1cerny)[<img alt="marvinosswald" src="https://avatars.githubusercontent.com/u/1621844?v=4&s=117" width="117">](https://github.com/marvinosswald)[<img alt="nick1699" src="https://avatars.githubusercontent.com/u/50705000?v=4&s=117" width="117">](https://github.com/nick1699)
|
368 |
|
369 | [<img alt="paulyoder" src="https://avatars.githubusercontent.com/u/224111?v=4&s=117" width="117">](https://github.com/paulyoder)[<img alt="reda-alaoui" src="https://avatars.githubusercontent.com/u/2890843?v=4&s=117" width="117">](https://github.com/reda-alaoui)[<img alt="remiburtin" src="https://avatars.githubusercontent.com/u/4236675?v=4&s=117" width="117">](https://github.com/remiburtin)[<img alt="gingters" src="https://avatars.githubusercontent.com/u/755148?v=4&s=117" width="117">](https://github.com/gingters)[<img alt="kranich" src="https://avatars.githubusercontent.com/u/7249754?v=4&s=117" width="117">](https://github.com/kranich)
|
370 |
|
371 | [<img alt="StefanoChiodino" src="https://avatars.githubusercontent.com/u/1428893?v=4&s=117" width="117">](https://github.com/StefanoChiodino)[<img alt="tpeter1985" src="https://avatars.githubusercontent.com/u/16336536?v=4&s=117" width="117">](https://github.com/tpeter1985)[<img alt="dennisameling" src="https://avatars.githubusercontent.com/u/17739158?v=4&s=117" width="117">](https://github.com/dennisameling)[<img alt="dependabot[bot]" src="https://avatars.githubusercontent.com/in/29110?v=4&s=117" width="117">](https://github.com/apps/dependabot)[<img alt="jdgeier" src="https://avatars.githubusercontent.com/u/949299?v=4&s=117" width="117">](https://github.com/jdgeier)
|
372 |
|
373 | [<img alt="mraible" src="https://avatars.githubusercontent.com/u/17892?v=4&s=117" width="117">](https://github.com/mraible)[<img alt="ajpierson" src="https://avatars.githubusercontent.com/u/56389?v=4&s=117" width="117">](https://github.com/ajpierson)[<img alt="artnim" src="https://avatars.githubusercontent.com/u/414375?v=4&s=117" width="117">](https://github.com/artnim)[<img alt="fmalcher" src="https://avatars.githubusercontent.com/u/1683147?v=4&s=117" width="117">](https://github.com/fmalcher)[<img alt="Flofie" src="https://avatars.githubusercontent.com/u/12624982?v=4&s=117" width="117">](https://github.com/Flofie)
|
374 |
|
375 | [<img alt="mabdelaal86" src="https://avatars.githubusercontent.com/u/11019219?v=4&s=117" width="117">](https://github.com/mabdelaal86)[<img alt="nhance" src="https://avatars.githubusercontent.com/u/602226?v=4&s=117" width="117">](https://github.com/nhance)[<img alt="Razzeee" src="https://avatars.githubusercontent.com/u/5943908?v=4&s=117" width="117">](https://github.com/Razzeee)[<img alt="maxisam" src="https://avatars.githubusercontent.com/u/456807?v=4&s=117" width="117">](https://github.com/maxisam)[<img alt="ismcagdas" src="https://avatars.githubusercontent.com/u/4133525?v=4&s=117" width="117">](https://github.com/ismcagdas)
|
376 |
|
377 | [<img alt="Toxicable" src="https://avatars.githubusercontent.com/u/13490925?v=4&s=117" width="117">](https://github.com/Toxicable)[<img alt="ManuelRauber" src="https://avatars.githubusercontent.com/u/740791?v=4&s=117" width="117">](https://github.com/ManuelRauber)[<img alt="vdveer" src="https://avatars.githubusercontent.com/u/1217814?v=4&s=117" width="117">](https://github.com/vdveer)[<img alt="jeroenheijmans" src="https://avatars.githubusercontent.com/u/1590536?v=4&s=117" width="117">](https://github.com/jeroenheijmans)[<img alt="manfredsteyer" src="https://avatars.githubusercontent.com/u/1573728?v=4&s=117" width="117">](https://github.com/manfredsteyer)
|
378 |
|
379 |
|