Skip to content

Automatic token refresh #39

Open
Open

Description

@RaynoldVanHeyningen

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?

Activity

  1. FergaliciousPixelicious commented on Jun 22, 2024

    @FergaliciousPixelicious

    @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

  2. RaynoldVanHeyningen commented on Jun 26, 2024

    @RaynoldVanHeyningen
    Author

    @FergaliciousPixelicious Thanks, but this is not open-source, it's managed payware, so completely unrelated to this project.

    I've built my own solution.

  3. jtoy commented on Jul 2, 2024

    @jtoy

    @RaynoldVanHeyningen what is your solution?

  4. RaynoldVanHeyningen commented on Jul 2, 2024

    @RaynoldVanHeyningen
    Author

    @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
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions