1{{--
2 Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
3 See the LICENCE file in the repository root for full licence text.
4--}}
5@php
6 use App\Libraries\ApidocRouteHelper;
7 use Knuckles\Camel\Output\OutputEndpointData;
8
9 $baseUrl = $GLOBALS['cfg']['app']['url'];
10 $wikiUrl = wiki_url('Bot_account', null, false);
11
12 $defaultHeaders = [
13 'Accept' => 'application/json',
14 'Content-Type' => 'application/x-www-form-urlencoded',
15 ];
16@endphp
17
18<h1>Authentication</h1>
19
20<p>
21 Routes marked with the <a class="badge badge-scope badge-scope-oauth" name="scope-oauth">OAuth</a> label require a valid OAuth2 token for access.
22</p>
23
24<p>
25 More information about applications you have registered and granted permissions to can be found <a href="#managing-oauth-applications">here</a>.
26</p>
27
28<p>
29 The API supports the following grant types:
30 <ul>
31 <li><a href="https://oauth.net/2/grant-types/authorization-code/">Authorization Code Grant</a>
32 <li><a href="https://oauth.net/2/grant-types/client-credentials/">Client Credentials Grant</a>
33 </ul>
34</p>
35
36<p>
37 Before you can use the osu!api, you will need to
38 <ol>
39 <li>have registered an OAuth Application.
40 <li>
41 acquire an access token by either:
42 <ul>
43 <li>authorizing users for your application;
44 <li>requesting Client Credentials token.
45 </ul>
46 </ol>
47</p>
48
49
50<h2>Registering an OAuth application</h2>
51
52<p>
53 Before you can get an OAuth token, you will need to register an OAuth application on your <a href="{{ route('account.edit').'#new-oauth-application' }}">account settings page</a>.
54<p>
55
56<p>
57 To register an OAuth application you will need to provide the:
58</p>
59
60<table>
61 <thead>
62 <tr>
63 <th>Name</th>
64 <th>Description</th>
65 </tr>
66 </thead>
67 <tbody>
68 <tr>
69 <td>Application Name</td>
70 <td>
71 This is the name that will be visible to users of your application. The name of your application cannot be changed.
72 </td>
73 </tr>
74 <tr>
75 <td>Application Callback URL</td>
76 <td>
77 The URL in your application where users will be sent after authorization.
78 </td>
79 </tr>
80 </tbody>
81</table>
82
83<p>
84 The <code>Application Callback URL</code> is required when for using <a href="#authorization-code-grant">Authorization Codes</a>.
85 This may be left blank if you are only using <a href="#client-credentials-grant">Client Credentials Grants</a>.
86</p>
87
88<p>
89 Your new OAuth application will have a <code>Client ID</code> and <code>Client Secret</code>; the <code>Client Secret</code> is like a password for your OAuth application, it should be kept private and <strong>do not share it with anyone else</strong>.
90</p>
91
92
93<h2>Authorization Code Grant</h2>
94
95<p>
96 The flow to authorize users for your application is:
97 <ol>
98 <li>Requesting authorization from users
99 <li>Users are redirected back to your site
100 <li>Your application accesses the API with the user's access token
101 </ol>
102</p>
103
104<aside class="notice">
105Restricted users can grant authorization like anyone else. If your client should not support restricted users, it can check <code>is_restricted</code> from the <a href="#get-own-data">Get Own Data</a> response.
106</aside>
107
108
109<h3>Request authorization from a user</h3>
110
111@php
112 $description = 'To obtain an access token, you must first get an authorization code that is created when a user grants permissions to your application. To request permission from the user, they should be redirected to:';
113 $uri = route('oauth.authorizations.authorize', null, false);
114 $endpoint = new OutputEndpointData([
115 'metadata' => ['authenticated' => false, 'description' => $description],
116 'methods' => ['GET'],
117 'httpMethods' => ['GET'],
118 'uri' => $uri,
119 'queryParameters' => [
120 'client_id' => [
121 'description' => 'The Client ID you received when you [registered]('.route('account.edit').'#new-oauth-application).',
122 'name' => 'client_id',
123 'required' => true,
124 'type' => 'integer',
125 'example' => 1,
126 ],
127 'redirect_uri' => [
128 'description' => 'The URL in your application where users will be sent after authorization. This must match the registered Application Callback URL exactly.',
129 'name' => 'redirect_uri',
130 'example' => 'http://localhost:4000',
131 ],
132 'response_type' => [
133 'description' => 'This should always be `code` when requesting authorization.',
134 'name' => 'response_type',
135 'required' => true,
136 'example' => 'code',
137 ],
138 'scope' => [
139 'description' => 'A space-delimited string of [scopes](#scopes).',
140 'name' => 'scope',
141 'required' => false,
142 'example' => 'public identify',
143 ],
144 'state' => [
145 'description' => 'Data that will be returned when a temporary code is issued. It can be used to provide a token for protecting against cross-site request forgery attacks.',
146 'name' => 'state',
147 'required' => false,
148 'example' => 'randomval',
149 ],
150 ],
151 ]);
152@endphp
153@include('docs.endpoint', [
154 'endpoint' => $endpoint,
155 'showEndpointTitle' => false,
156 'showRequestTitle' => false,
157])
158
159<h3>User is redirected back to your site</h3>
160
161@php
162 $description = <<<EOT
163 If the user accepts your request, they will be redirected back to your site with a temporary single-use `code` contained in the URL parameter.
164 If a `state` value was provided in the previous request, it will be returned here.
165
166 <aside class="notice">
167 If you are using `state` as protection against CSRF attacks, your OAuth client is responsible for validating this value.
168 </aside>
169
170 Exchange this `code` for an access token:
171
172 ---
173
174 ### Response Format
175
176 Successful requests will be issued an access token:
177
178 Name | Type | Description
179 --------------|---------|-----------------------------
180 token_type | string | The type of token, this should always be `Bearer`.
181 expires_in | integer | The number of seconds the token will be valid for.
182 access_token | string | The access token.
183 refresh_token | string | The refresh token.
184 EOT;
185 $uri = route('oauth.passport.token', null, false);
186 $endpoint = new OutputEndpointData([
187 'bodyParameters' => [
188 'client_id' => [
189 'description' => 'The client ID of your application.',
190 'name' => 'client_id',
191 'required' => true,
192 'example' => 1,
193 ],
194 'client_secret' => [
195 'description' => 'The client secret of your application.',
196 'name' => 'client_secret',
197 'required' => true,
198 'example' => 'clientsecret',
199 ],
200 'code' => [
201 'description' => 'The code you received.',
202 'name' => 'code',
203 'required' => true,
204 'example' => 'receivedcode',
205 ],
206 'grant_type' => [
207 'description' => 'This must always be `authorization_code`',
208 'name' => 'grant_type',
209 'required' => true,
210 'example' => 'authorization_code',
211 ],
212 'redirect_uri' => [
213 'description' => 'This must be the same as the one used on authorization request.',
214 'name' => 'redirect_uri',
215 'required' => false,
216 'example' => 'http://localhost:4000',
217 ],
218 ],
219 'boundUri' => $uri,
220 'cleanQueryParameters' => [],
221 'fileParameters' => [],
222 'headers' => $defaultHeaders,
223 'metadata' => ['authenticated' => false, 'description' => $description],
224 'httpMethods' => ['POST'],
225 'queryParameters' => [],
226 'responses' => [
227 [
228 'content' => [
229 'access_token' => 'verylongstring',
230 'expires_in' => 86400,
231 'refresh_token' => 'anotherlongstring',
232 'token_type' => 'Bearer',
233 ],
234 'status' => 200,
235 ],
236 ],
237 'showresponse' => true,
238 'uri' => $uri,
239 'urlParameters' => [],
240 ]);
241@endphp
242@include('docs.endpoint', [
243 'endpoint' => $endpoint,
244 'showEndpointTitle' => false,
245 'showRequestTitle' => false,
246])
247
248<h3>Refresh access token</h3>
249
250@php
251 $description = <<<EOT
252 Access token expires after some time as per `expires_in` field. Refresh the token to get new access token without going through authorization process again.
253
254 Use `refresh_token` received during previous access token request:
255
256 ---
257
258 ### Response Format
259
260 Successful requests will be issued an access token and a new refresh token:
261
262 Name | Type | Description
263 --------------|---------|-----------------------------
264 token_type | string | The type of token, this should always be `Bearer`.
265 expires_in | integer | The number of seconds the token will be valid for.
266 access_token | string | The access token.
267 refresh_token | string | The refresh token.
268 EOT;
269 $uri = route('oauth.passport.token', null, false);
270 $endpoint = new OutputEndpointData([
271 'bodyParameters' => [
272 'client_id' => [
273 'description' => 'The Client ID you received when you [registered]('.route('account.edit').'#new-oauth-application).',
274 'name' => 'client_id',
275 'required' => true,
276 'type' => 'integer',
277 'example' => 1,
278 ],
279 'client_secret' => [
280 'description' => 'The client secret of your application.',
281 'name' => 'client_secret',
282 'required' => true,
283 'type' => 'string',
284 'example' => 'clientsecret',
285 ],
286 'grant_type' => [
287 'description' => 'This must always be `refresh_token`.',
288 'name' => 'grant_type',
289 'required' => true,
290 'type' => 'string',
291 'example' => 'refresh_token',
292 ],
293 'refresh_token' => [
294 'description' => 'Value of refresh token received from previous access token request.',
295 'name' => 'refresh_token',
296 'required' => true,
297 'type' => 'string',
298 'example' => 'longstring',
299 ],
300 'scope' => [
301 'description' => "A space-delimited string of [scopes](#scopes). Specifying fewer scopes than existing access token is allowed but subsequent refresh tokens can't re-add removed scopes. If this isn't specified, existing access token scopes will be used.",
302 'name' => 'scope',
303 'required' => false,
304 'example' => 'public identify',
305 ],
306 ],
307 'boundUri' => $uri,
308 'cleanQueryParameters' => [],
309 'fileParameters' => [],
310 'headers' => $defaultHeaders,
311 'metadata' => ['authenticated' => false, 'description' => $description],
312 'httpMethods' => ['POST'],
313 'queryParameters' => [],
314 'responses' => [
315 [
316 'content' => [
317 'access_token' => 'verylongstring',
318 'expires_in' => 86400,
319 'refresh_token' => 'anotherlongstring',
320 'token_type' => 'Bearer',
321 ],
322 'status' => 200,
323 ],
324 ],
325 'showresponse' => true,
326 'uri' => $uri,
327 'urlParameters' => [],
328 ]);
329@endphp
330@include('docs.endpoint', [
331 'endpoint' => $endpoint,
332 'showEndpointTitle' => false,
333 'showRequestTitle' => false,
334])
335
336<h2>Client Credentials Grant</h2>
337
338@php
339 $description = <<<EOT
340 The client credential flow provides a way for developers to get access tokens that do not have associated user permissions; as such, these tokens are considered as guest users.
341
342 Example for requesting Client Credentials token:
343
344 ---
345
346 ### Response Format
347
348 Successful requests will be issued an access token:
349
350 Name | Type | Description
351 --------------|---------|-----------------------------
352 token_type | string | The type of token, this should always be `Bearer`.
353 expires_in | integer | The number of seconds the token will be valid for.
354 access_token | string | The access token.
355 EOT;
356 $uri = route('oauth.passport.token', null, false);
357 $endpoint = new OutputEndpointData([
358 'bodyParameters' => [
359 'client_id' => [
360 'description' => 'The Client ID you received when you [registered]('.route('account.edit').'#new-oauth-application).',
361 'name' => 'client_id',
362 'required' => true,
363 'type' => 'integer',
364 'example' => 1,
365 ],
366 'client_secret' => [
367 'description' => 'The client secret of your application.',
368 'name' => 'client_secret',
369 'required' => true,
370 'type' => 'string',
371 'example' => 'clientsecret',
372 ],
373 'grant_type' => [
374 'description' => 'This must always be `client_credentials`.',
375 'name' => 'grant_type',
376 'required' => true,
377 'type' => 'string',
378 'example' => 'client_credentials',
379 ],
380 'scope' => [
381 'description' => 'Must be `public`; other scopes have no meaningful effect.',
382 'name' => 'scope',
383 'required' => true,
384 'type' => 'string',
385 'example' => 'public',
386 ],
387 ],
388 'boundUri' => $uri,
389 'cleanQueryParameters' => [],
390 'fileParameters' => [],
391 'headers' => $defaultHeaders,
392 'metadata' => ['authenticated' => false, 'description' => $description],
393 'httpMethods' => ['POST'],
394 'queryParameters' => [],
395 'responses' => [
396 [
397 'content' => [
398 'access_token' => 'verylongstring',
399 'expires_in' => 86400,
400 'token_type' => 'Bearer',
401 ],
402 'status' => 200,
403 ],
404 ],
405 'showresponse' => true,
406 'uri' => $uri,
407 'urlParameters' => [],
408 ]);
409@endphp
410@include('docs.endpoint', [
411 'endpoint' => $endpoint,
412 'showEndpointTitle' => false,
413 'showRequestTitle' => false,
414])
415
416<h2>Using the access token to access the API</h2>
417
418<p>
419 With the access token, you can make requests to osu!api on behalf of a user.
420</p>
421
422<p>
423 The token should be included in the header of requests to the API.
424</p>
425
426<p>
427 <code>Authorization: Bearer @{{token}}</code>
428</p>
429
430<div class="bash-example">
431 <pre><code class="language-bash"
432># With shell, you can just pass the correct header with each request
433curl "{{ $GLOBALS['cfg']['app']['url'] }}/api/[version]/[endpoint]"
434 -H "Authorization: Bearer @{{token}}"</code><pre>
435</div>
436
437<div class="javascript-example">
438 <pre><code class="language-javascript"
439>// This javascript example uses fetch()
440fetch("{{ $GLOBALS['cfg']['app']['url'] }}/api/[version]/[endpoint]", {
441 headers: {
442 Authorization: 'Bearer @{{token}}'
443 }
444});</code></pre>
445</div>
446
447<blockquote><p>Make sure to replace <code>@{{token}}</code> with your OAuth2 token.</p></blockquote>
448
449<aside class="notice">
450 You must replace <code>@{{token}}</code> with your OAuth2 token.
451</aside>
452
453
454<h2>Resource Owner</h2>
455
456<p>
457 The <code>Resource Owner</code> is the user that a token acts on behalf of.
458</p>
459
460<p>
461 For <a href="#authorization-code-grant">Authorization Code Grant</a> tokens, the Resource Owner is the user authorizing the token.
462</p>
463
464<p>
465 <a href="#client-credentials-grant">Client Credentials Grant</a> tokens do not have a Resource Owner (i.e. is a guest user), unless they have been granted the {{ ApidocRouteHelper::scopeBadge('delegate') }} scope. The Resource Owner of tokens with the {{ ApidocRouteHelper::scopeBadge('delegate') }} scope is the owner of the OAuth Application that was granted the token.
466</p>
467
468<p>
469 Routes marked with <span class='badge badge-scope badge-user'>requires user</span> require the use of tokens that have a Resource Owner.
470</p>
471
472
473<h2>Client Credentials Delegation</h2>
474
475<p>
476 Client Credentials Grant tokens may be allowed to act on behalf of the owner of the OAuth client (delegation) by requesting the {{ ApidocRouteHelper::scopeBadge('delegate') }} scope, in addition to other scopes supporting delegation.
477 When using delegation, scopes that support delegation cannot be used together with scopes that do not support delegation.
478 Delegation is only available to <a href="{{ $wikiUrl }}">Chat Bot</a>s.
479</p>
480
481<p>
482 The following scopes currently support delegation:
483</p>
484
485<table>
486 <thead>
487 <tr>
488 <th>Name</th>
489 </tr>
490 </thead>
491 <tbody>
492 <tr>
493 <td>{{ ApidocRouteHelper::scopeBadge('chat.write') }}</td>
494 </tr>
495 </tbody>
496</table>
497
498<h2>Scopes</h2>
499
500<p>
501 The following scopes are currently supported:
502</p>
503
504@php
505$scopeDescriptions = [
506 'chat.read' => "Allows read chat messages on a user's behalf.",
507 'chat.write' => "Allows sending chat messages on a user's behalf.",
508 'chat.write_manage' => "Allows joining and leaving chat channels on a user's behalf.",
509 'delegate' => "Allows acting as the owner of a client; only available for [Client Credentials Grant](#client-credentials-grant).",
510 'forum.write' => "Allows creating and editing forum posts on a user's behalf.",
511 'friends.read' => 'Allows reading of the user\'s friend list.',
512 'identify' => 'Allows reading of the public profile of the user (`/me`).',
513 'public' => 'Allows reading of publicly available data on behalf of the user.',
514];
515@endphp
516
517<table>
518 <thead>
519 <tr>
520 <th>Name</th>
521 <th>Description</th>
522 </tr>
523 </thead>
524 <tbody>
525 @foreach ($scopeDescriptions as $scope => $description)
526 <tr>
527 <td>
528 <a class="badge badge-scope badge-scope-{{ $scope }}" name="scope-{{ $scope }}">{{ $scope }}</a>
529 </td>
530 <td>{!! markdown_plain($description) !!}</td>
531 </tr>
532 @endforeach
533 <tr>
534 </tr>
535 </tbody>
536</table>
537
538<p>
539 <code>identify</code> is the default scope for the <a href="#authorization-code-grant">Authorization Code Grant</a> and always implicitly provided. The <a href="#client-credentials-grant">Client Credentials Grant</a> does not currently have any default scopes.
540</p>
541
542<p>
543 Routes marked with <a class="badge badge-scope badge-scope-lazer" name="scope-lazer">lazer</a> are intended for use by the <a href="https://github.com/ppy/osu">osu!lazer</a> client and not currently available for use with Authorization Code or Client Credentials grants.
544</p>
545
546<p>
547 Using the {{ ApidocRouteHelper::scopeBadge('chat.write') }} scope requires either
548 <ul>
549 <li>a <a href="{{ $wikiUrl }}">Chat Bot</a> account to send messages on behalf of other users.
550 <li>Authorization code grant where the user is the same as the client's owner (send as yourself).
551 </ul>
552</p>
553
554
555<h2>Managing OAuth applications</h2>
556
557<p>
558 Your <a href="{{ route('account.edit').'#oauth' }}">account settings</a> page will show your registered OAuth applications, and all the OAuth applications you have granted permissions to.
559</p>
560
561<h3>Reset Client Secret</h3>
562
563<p>
564 You can generate a new <code>Client Secret</code> by choosing to "Reset client secret", however, this will disable all access tokens issued for the application.
565</p>