Skip to content

Commit a6f2baa

Browse files
committed
More docs regarding auth
1 parent 7bc5015 commit a6f2baa

9 files changed

+312
-4
lines changed

SpotifyAPI.Docs/docs/auth_introduction.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,18 @@ id: auth_introduction
33
title: Introduction
44
---
55

6-
Hello
6+
Spotify does not allow unauthorized access to the api. Thus, you need an access token to make requets. This access token can be gathered via multiple schemes, all following the OAuth2 spec. Since it's important to choose the correct scheme for your usecase, make sure you have a grasp of the following terminology/docs:
7+
8+
* OAuth2
9+
* [Spotify Authorization Flows](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow)
10+
11+
Since every auth flow also needs an application in the [spotify dashboard](https://developer.spotify.com/dashboard/), make sure you have the necessary values (like `Client Id` and `Client Secret`).
12+
13+
Then, continue with the docs of the specific auth flows:
14+
15+
* [Client Credentials](client_credentials)
16+
* [Implicit Grant](implicit_grant)
17+
* [Authorization Code](authorization_code)
18+
* Token Swap
19+
20+
![auth comparison](/img/auth_comparison.png)
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
id: authorization_code
3+
title: Authorization Code
4+
---
5+
6+
> This flow is suitable for long-running applications in which the user grants permission only once. It provides an access token that can be refreshed. Since the token exchange involves sending your secret key, perform this on a secure location, like a backend service, and not from a client such as a browser or from a mobile app.
7+
8+
## Existing Web-Server
9+
10+
If you are already in control of a Web-Server (like `ASP.NET`), you can start the flow by generating a login uri
11+
12+
```csharp
13+
// Make sure "http://localhost:5000" is in your applications redirect URIs!
14+
var loginRequest = new LoginRequest(
15+
new Uri("http://localhost:5000"),
16+
"ClientId",
17+
LoginRequest.ResponseType.Code
18+
)
19+
{
20+
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
21+
};
22+
var uri = loginRequest.ToUri();
23+
// Redirect user to uri via your favorite web-server
24+
```
25+
26+
When the user is redirected to the generated uri, he will have to login with his spotify account and confirm, that your application wants to access his user data. Once confirmed, he will be redirect to `http://localhost:5000` and a `code` parameter is attached to the query. This `code` has to be exchanged for an `access_token` and `refresh_token`:
27+
28+
```csharp
29+
// This method should be called from your web-server when the user visits "http://localhost:5000"
30+
public Task GetCallback(string code)
31+
{
32+
var response = await new OAuthClient().RequestToken(
33+
new AuthorizationCodeTokenRequest("ClientId", "ClientSecret", code, "http://localhost:5000")
34+
);
35+
36+
var spotify = new SpotifyClient(response.AccessToken);
37+
// Also important for later: response.RefreshToken
38+
}
39+
```
40+
41+
If the token expires at some point (check via `response.IsExpired`), you can refresh it:
42+
43+
```csharp
44+
var newResponse = await new OAuthClient().RequestToken(
45+
new AuthorizationCodeRefreshRequest("ClientId", "ClientSecret", response.RefreshToken)
46+
);
47+
48+
var spotify = new SpotifyClient(newResponse.AccessToken);
49+
```
50+
51+
You can also let the `AuthorizationCodeAuthenticator` take care of the refresh part:
52+
53+
```csharp
54+
var response = await new OAuthClient().RequestToken(
55+
new AuthorizationCodeTokenRequest("ClientId", "ClientSecret", code, "http://localhost:5000")
56+
);
57+
var config = SpotifyClientConfig
58+
.CreateDefault()
59+
.WithAuthenticator(new AuthorizationCodeAuthenticator("ClientId", "ClientSecret", response));
60+
61+
var spotify = new SpotifyClient(config);
62+
```
63+
64+
## Using Spotify.Web.Auth
65+
66+
For cross-platform CLI and desktop apps (non `UWP` apps), `Spotify.Web.Auth` can be used to supply a small embedded Web Server for the code retrieval.
67+
68+
:::warning
69+
You're client secret will be exposed when embedded in a desktop/cli app. This can be abused and is not preffered. If possible, let the user create an application in spotify dashboard or let a server handle the spotify communication.
70+
:::
71+
72+
```csharp
73+
private static EmbedIOAuthServer _server;
74+
75+
public static async Task Main()
76+
{
77+
// Make sure "http://localhost:5000/callback" is in your spotify application as redirect uri!
78+
_server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000);
79+
await _server.Start();
80+
81+
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
82+
83+
var request = new LoginRequest(_server.BaseUri, "ClientId", LoginRequest.ResponseType.Code)
84+
{
85+
Scope = new List<string> { Scopes.UserReadEmail }
86+
};
87+
BrowserUtil.Open(uri);
88+
}
89+
90+
private static async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response)
91+
{
92+
await _server.Stop();
93+
94+
var config = SpotifyClientConfig.CreateDefault();
95+
var tokenResponse = await new OAuthClient(config).RequestToken(
96+
new AuthorizationCodeTokenRequest(
97+
"ClientId", "ClientSecret", response.Code, "http://localhost:5000/callback"
98+
)
99+
);
100+
101+
var spotify = new SpotifyClient(tokenResponse.AccessToken);
102+
// do calls with spotify and save token?
103+
}
104+
```
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
id: client_credentials
3+
title: Client Credentials
4+
---
5+
6+
> The Client Credentials flow is used in server-to-server authentication.
7+
> Only endpoints that do not access user information can be accessed.
8+
9+
By supplying your `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET`, you get an access token.
10+
11+
## Request token once
12+
13+
To request an access token, build a `ClientCredentialsRequest` and send it via `OAuthClient`. This access token will expire after some time and you need to repeat the process.
14+
15+
```csharp
16+
public static async Task Main()
17+
{
18+
var config = SpotifyClientConfig.CreateDefault();
19+
20+
var request = new ClientCredentialsRequest("CLIENT_ID", "CLIENT_SECRET");
21+
var response = await new OAuthClient(config).RequestToken(request);
22+
23+
var spotify = new SpotifyClient(config.WithToken(response.AccessToken));
24+
}
25+
```
26+
27+
## Request Token On-Demand
28+
29+
You can also use `CredentialsAuthenticator`, which will make sure the spotify instance will always have an up-to-date access token by automatically refreshing the token on-demand.
30+
31+
```csharp
32+
public static async Task Main()
33+
{
34+
var config = SpotifyClientConfig
35+
.CreateDefault()
36+
.WithAuthenticator(new CredentialsAuthenticator("CLIENT_ID", "CLIENT_SECRET"));
37+
38+
var spotify = new SpotifyClient(config);
39+
}
40+
```
41+
42+
:::info
43+
There is no thread safety guaranteed when using `CredentialsAuthenticator`.
44+
:::
45+
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
id: implicit_grant
3+
title: Implicit Grant
4+
---
5+
6+
> Implicit grant flow is for clients that are implemented entirely using JavaScript and running in the resource owner’s browser. You do not need any server-side code to use it. Rate limits for requests are improved but there is no refresh token provided. This flow is described in RFC-6749.
7+
8+
This flow is useful for getting a user access token for a short timespan
9+
10+
## Existing Web-Server
11+
12+
If you are already in control of a Web-Server (like `ASP.NET`), you can start the flow by generating a login uri
13+
14+
```csharp
15+
// Make sure "http://localhost:5000" is in your applications redirect URIs!
16+
var loginRequest = new LoginRequest(
17+
new Uri("http://localhost:5000"),
18+
"ClientId",
19+
LoginRequest.ResponseType.Token
20+
)
21+
{
22+
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
23+
};
24+
var uri = loginRequest.ToUri();
25+
// Redirect user to uri via your favorite web-server
26+
```
27+
28+
When the user is redirected to the generated uri, he will have to login with his spotify account and confirm, that your application wants to access his user data. Once confirmed, he will be redirect to `http://localhost:5000` and the fragment identifier (`#` part of URI) will contain an access token.
29+
30+
:::warning
31+
Note, this parameter is not sent to the server! You need JavaScript to access it.
32+
:::
33+
34+
## Using custom Protocols
35+
36+
This flow can also be used with custom protocols instead of `http`/`https`. This is especially interesting for `UWP` apps, since your able to register custom protocol handlers quite easily.
37+
38+
![protocol handlers](/img/auth_protocol_handlers.png)
39+
40+
The process is very similar, you generate a uri and open it for the user
41+
42+
```csharp
43+
// Make sure "spotifyapi.web.oauth://token" is in your applications redirect URIs!
44+
var loginRequest = new LoginRequest(
45+
new Uri("spotifyapi.web.oauth://token"),
46+
"ClientId",
47+
LoginRequest.ResponseType.Token
48+
)
49+
{
50+
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
51+
};
52+
var uri = loginRequest.ToUri();
53+
54+
// This call requires Spotify.Web.Auth
55+
BrowserUtil.Open(uri);
56+
```
57+
58+
After the user logged in and consented your app, your `UWP` app will receive a callback:
59+
60+
```csharp
61+
protected override void OnActivated(IActivatedEventArgs args)
62+
{
63+
if (args.Kind == ActivationKind.Protocol)
64+
{
65+
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
66+
var publisher = Mvx.IoCProvider.Resolve<ITokenPublisherService>();
67+
68+
// This Uri contains your access token in the Fragment part
69+
Console.WriteLine(eventArgs.Uri);
70+
}
71+
}
72+
```
73+
74+
For a real example, have a look at the [UWP Example](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/v6/SpotifyAPI.Web.Examples/Example.UWP)
75+
76+
# Using Spotify.Web.Auth
77+
78+
For cross-platform CLI and desktop apps (non `UWP` apps), custom protocol handlers are sometimes not an option. The fallback here is a small cross-platform embedded web server running on `http://localhost:5000` serving javascript. The javscript will parse the fragment part of the URI and sends a request to the web server in the background. The web server then notifies your appliciation via event.
79+
80+
```csharp
81+
private static EmbedIOAuthServer _server;
82+
83+
public static async Task Main()
84+
{
85+
// Make sure "http://localhost:5000/callback" is in your spotify application as redirect uri!
86+
_server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000);
87+
await _server.Start();
88+
89+
_server.ImplictGrantReceived += OnImplictGrantReceived;
90+
91+
var request = new LoginRequest(_server.BaseUri, "ClientId", LoginRequest.ResponseType.Code)
92+
{
93+
Scope = new List<string> { Scopes.UserReadEmail }
94+
};
95+
BrowserUtil.Open(uri);
96+
}
97+
98+
private static async Task OnImplictGrantReceived(object sender, ImplictGrantResponse response)
99+
{
100+
await _server.Stop();
101+
var spotify = new SpotifyClient(response.AccessToken);
102+
// do calls with spotify
103+
}
104+
```
105+
106+
For real examples, have a look at [Example.CLI.PersistentConfig](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/v6/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig) and [Example.CLI.CustomHTML](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/v6/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML)

SpotifyAPI.Docs/docs/unit_testing.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
id: unit_testing
3+
title: Unit Testing
4+
---
5+
6+
The modular structure of the library makes it easy to mock the API when unit testing. Consider the following method:
7+
8+
```csharp
9+
public static async Task<bool> IsAdmin(IUserProfileClient userProfileClient)
10+
{
11+
// get loggedin user
12+
var user = await userProfileClient.Current();
13+
14+
// only my user id is an admin
15+
return user.Id == "1122095781";
16+
}
17+
```
18+
19+
Using `Moq`, this can be tested without doing any network requests:
20+
21+
```csharp
22+
[Test]
23+
public async Task IsAdmin_SuccessTest()
24+
{
25+
var userProfileClient = new Mock<IUserProfileClient>();
26+
userProfileClient.Setup(u => u.Current()).Returns(
27+
Task.FromResult(new PrivateUser
28+
{
29+
Id = "1122095781"
30+
})
31+
);
32+
33+
Assert.AreEqual(true, await IsAdmin(userProfileClient.Object));
34+
}
35+
```

SpotifyAPI.Docs/sidebars.js

+4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ module.exports = {
1313
'proxy',
1414
'pagination',
1515
'retry_handling',
16+
'unit_testing'
1617
]
1718
},
1819
{
1920
type: 'category',
2021
label: 'Authentication Guides',
2122
items: [
2223
'auth_introduction',
24+
'client_credentials',
25+
'implicit_grant',
26+
'authorization_code'
2327
]
2428
},
2529
{
15.1 KB
Loading
Loading

SpotifyAPI.Web.Tests/Http/SimpleRetryHandlerTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public async Task HandleRetry_TooManyRequestsWithNoSuccess()
3636

3737
Assert.AreEqual(2, retryCalled);
3838
Assert.AreEqual(setup.Response.Object, response);
39-
setup.Sleep.Verify(s => s(TimeSpan.FromMilliseconds(50)), Times.Exactly(2));
39+
setup.Sleep.Verify(s => s(TimeSpan.FromSeconds(50)), Times.Exactly(2));
4040
}
4141

4242
[Test]
@@ -67,7 +67,7 @@ public async Task HandleRetry_TooManyRetriesWithSuccess()
6767

6868
Assert.AreEqual(1, retryCalled);
6969
Assert.AreEqual(successResponse.Object, response);
70-
setup.Sleep.Verify(s => s(TimeSpan.FromMilliseconds(50)), Times.Once);
70+
setup.Sleep.Verify(s => s(TimeSpan.FromSeconds(50)), Times.Once);
7171
}
7272

7373
[Test]
@@ -97,7 +97,7 @@ public async Task HandleRetry_TooManyRetriesWithSuccessNoConsume()
9797

9898
Assert.AreEqual(1, retryCalled);
9999
Assert.AreEqual(successResponse.Object, response);
100-
setup.Sleep.Verify(s => s(TimeSpan.FromMilliseconds(50)), Times.Once);
100+
setup.Sleep.Verify(s => s(TimeSpan.FromSeconds(50)), Times.Once);
101101
}
102102

103103
[Test]

0 commit comments

Comments
 (0)