-
Notifications
You must be signed in to change notification settings - Fork 234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automatic token refresh #39
Comments
@RaynoldVanHeyningen in case you're interested, check out sumosuno.com They are able to keep the token alive and it's a plug and play option in case you're interested |
@FergaliciousPixelicious Thanks, but this is not open-source, it's managed payware, so completely unrelated to this project. I've built my own solution. |
@RaynoldVanHeyningen what is your solution? |
i noticed there was a form of token refresh going on, the main in problem 'in my situation' was that i was running the SunoAPI in a serverless environment, so the refreshed token wasn't persisted. Since i'm running in Azure and using .NET for my application, i rebuild this Suno API solution into a custom .NET solution, hosted on Azure, with the main difference being that when a unauthorized request happens in the api, i refresh the session_id and cookie and store these in a redis cache. Afterwards the request will be executed again with the refreshed information. Here is the relevant code, but as i said this is in .NET: SunoCookieService.cs:
public class SunoCookieService : BackgroundService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<SunoCookieService> _logger;
private readonly IDistributedCache _cache;
public SunoCookie SunoAuth { get; }
private DateTime _tokenExpiryTime;
public SunoCookieService(IHttpClientFactory clientFactory, IConfiguration configuration, ILogger<SunoCookieService> logger, IDistributedCache cache)
{
_clientFactory = clientFactory;
_configuration = configuration;
_logger = logger;
_cache = cache;
SunoAuth = new SunoCookie();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"ExecuteAsync");
await InitializeFromCache();
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (DateTime.UtcNow >= _tokenExpiryTime)
{
await UpdateTokenAndSession();
}
else
{
await TouchSession();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating token or touching session");
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
private async Task InitializeFromCache()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"InitializeFromCache");
var sessionId = await _cache.GetStringAsync("Suno:SessionId");
var cookie = await _cache.GetStringAsync("Suno:Cookie");
var token = await _cache.GetStringAsync("Suno:Token");
var tokenExpiry = await _cache.GetStringAsync("Suno:TokenExpiry");
if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
{
sessionId = _configuration["SunoAPI:SessionId"];
cookie = _configuration["SunoAPI:Cookie"];
if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
{
throw new InvalidOperationException("SessionId and Cookie must be provided either in cache or configuration.");
}
await _cache.SetStringAsync("Suno:SessionId", sessionId);
await _cache.SetStringAsync("Suno:Cookie", cookie);
}
else
{
_logger.LogInformation("Initialized from cache");
}
SunoAuth.SetSessionId(sessionId);
SunoAuth.LoadCookie(cookie);
if (!string.IsNullOrEmpty(token))
{
SunoAuth.SetToken(token);
if (DateTime.TryParse(tokenExpiry, out var expiry))
{
_tokenExpiryTime = expiry;
}
_logger.LogInformation($"Initialized with cached token: {token}, expiry time: {_tokenExpiryTime}");
}
}
public async Task UpdateTokenAndSession()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"UpdateTokenAndSession");
var client = _clientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());
var response = await client.PostAsync(
$"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/tokens?_clerk_js_version=4.72.0-snapshot.vc141245",
null);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Token update failed with status code {StatusCode}. Attempting to refresh session.", response.StatusCode);
await RefreshSession(client);
return;
}
var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault();
if (!string.IsNullOrEmpty(setCookie))
{
SunoAuth.LoadCookie(setCookie);
await _cache.SetStringAsync("Suno:Cookie", setCookie);
}
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
var token = jsonResponse["jwt"]?.ToString();
var sessionId = ExtractSessionIdFromToken(token);
if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(sessionId))
{
SunoAuth.SetToken(token);
SunoAuth.SetSessionId(sessionId);
await _cache.SetStringAsync("Suno:Token", token);
await _cache.SetStringAsync("Suno:SessionId", sessionId);
// Set token expiry time assuming token expiry time is 30 minutes from now
_tokenExpiryTime = DateTime.UtcNow.AddMinutes(30); // Adjust according to actual token lifetime
await _cache.SetStringAsync("Suno:TokenExpiry", _tokenExpiryTime.ToString());
_logger.LogInformation($"Token updated successfully: {token}, new session ID: {sessionId}, new expiry time: {_tokenExpiryTime}");
}
}
private async Task TouchSession()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"TouchSession");
var client = _clientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());
var response = await client.PostAsync(
$"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/touch?_clerk_js_version=4.72.0-snapshot.vc141245",
null);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Touch session failed with status code {StatusCode}.", response.StatusCode);
return;
}
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
var session = jsonResponse["response"]?["id"]?.ToString();
if (!string.IsNullOrEmpty(session))
{
_logger.LogInformation($"Session touched successfully: {session}");
}
}
private string ExtractSessionIdFromToken(string token)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"ExtractSessionIdFromToken");
if (string.IsNullOrEmpty(token)) return null;
var parts = token.Split('.');
if (parts.Length != 3) return null;
var payload = parts[1];
var json = Base64UrlDecode(payload);
var jwtPayload = JObject.Parse(json);
return jwtPayload["sid"]?.ToString();
}
private static string Base64UrlDecode(string input)
{
input = input.Replace('-', '+').Replace('_', '/');
switch (input.Length % 4)
{
case 2: input += "=="; break;
case 3: input += "="; break;
}
var bytes = Convert.FromBase64String(input);
return Encoding.UTF8.GetString(bytes);
}
private async Task RefreshSession(HttpClient client)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"RefreshSession");
_logger.LogWarning("Session expired. Attempting to refresh...");
var newSessionId = _configuration["SunoAPI:SessionId"];
var newCookie = _configuration["SunoAPI:Cookie"];
SunoAuth.SetSessionId(newSessionId);
SunoAuth.LoadCookie(newCookie);
await _cache.SetStringAsync("Suno:SessionId", newSessionId);
await _cache.SetStringAsync("Suno:Cookie", newCookie);
_logger.LogInformation("Session refreshed successfully");
await UpdateTokenAndSession(); // Attempt to update the token again with the new session
}
} |
I am running the Suno API properly, however i notice every 7 days, i have to update my session id and cookie value.
In the readme it says: "Automatic token maintenance and keep-alive"
How can i make sure my token gets properly refreshed, without me having to manually update this information?
The text was updated successfully, but these errors were encountered: