gporter0205 / schwab-api-client

GNU General Public License v3.0
8 stars 1 forks source link

Unable to access refresh tokens #18

Open sivadosapati opened 2 months ago

sivadosapati commented 2 months ago

I'm unable to access refresh tokens. I followed the steps outlined here - https://github.com/gporter0205/schwab-api-client/wiki. I'm stuck at Step #9 and any help to unblock would be greatly appreciated.

gporter0205 commented 2 months ago

Can you give me a little more info about what you have tried and any errors or messages that you are seeing?

sivadosapati commented 2 months ago

Sure - I did the following steps

0 - I set up the Custom Token Handler that will print the refresh token and access token and

@PostConstruct
public void init() {
    System.out.println("Initializing SchwabApiClientHandler..");
    if (!schwabMarketDataApiClient.isInitialized()) {
        ClientTokenHandler clientTokenHandler = new ClientTokenHandler();
        SchwabAccount schwabAccount = new SchwabAccount();
        schwabAccount.setUserId(schwabUserId);
        // If you have saved your refresh token, pass it to the API client service.
        // Otherwise, you will have to generate one each time you run this example.`
        // schwabAccount.setRefreshToken(schwabRefreshToken);
        // schwabAccount.setRefreshExpiration(schwabRefreshExpiration);
        schwabMarketDataApiClient.init(schwabAccount, clientTokenHandler);
        System.out.println("Initialized SACH");
    }
}

public class ClientTokenHandler implements SchwabTokenHandler { @Override public void onAccessTokenChange(@NonNull SchwabAccount schwabAccount) { System.out.print("Access Token : "+schwabAccount.getAccessToken()); }

@Override
public void onRefreshTokenChange(@NonNull SchwabAccount schwabAccount) {
    // ...Do something to save the Refresh Token here
    System.out.print("Refresh Token : "+schwabAccount.getRefreshToken());
    System.out.print(" expires on ");
    System.out.println("Refresh Token Expires : "+schwabAccount.getRefreshExpiration());
}

}

1 - Accessed the authorization end point (from step #9) - http:/localhost:8080/oauth2/schwab/authorization?schwabUserId=<>&callback=https://127.0.0.1 2 - I got redirected to the login page and after clicking a few buttons I got redirected to the following URL - https://127.0.0.1/?code=xxx%40&session=9f731c62-d4b9-46cf-ba26-831c1ecd7da9&state=c708b031-8e24-48e9-8f74-a87341dc1039 3 - I then accessed this URL to process the code - http://localhost:8080/ouath2/schwab/code?code=C0.b2F1dGgyLmJkYy5zY2h3YWIuY29t.Jw8LD0jfA_CFPTVOTodWUl_z3-eLM5kJe1bFBrp6KEk%40&session=9f731c62-d4b9-46cf-ba26-831c1ecd7da9&state=c708b031-8e24-48e9-8f74-a87341dc1039 4 - I got an error in the browser with the following message This application has no explicit mapping for /error, so you are seeing this as a fallback. Mon Aug 26 17:19:07 CDT 2024 There was an unexpected error (type=Internal Server Error, status=500). 5 - I got an error on my console reactor.core.Exceptions$RetryExhaustedException: Retries exhausted: 3/3 at reactor.core.Exceptions.retryExhausted(Exceptions.java:308) at reactor.util.retry.RetryBackoffSpec.lambda$static$0(RetryBackoffSpec.java:68) at reactor.util.retry.RetryBackoffSpec.lambda$null$4(RetryBackoffSpec.java:560) at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:183) at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) at reactor.core.publisher.SinkManyEmitterProcessor.drain(SinkManyEmitterProcessor.java:476) at reactor.core.publisher.SinkManyEmitterProcessor.tryEmitNext(SinkManyEmitterProcessor.java:273) at reactor.core.publisher.SinkManySerialized.tryEmitNext(SinkManySerialized.java:100) at reactor.core.publisher.InternalManySink.emitNext(InternalManySink.java:27) at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:194) at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:241) at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:315) at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106) at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:280) at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:232) at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:204) at reactor.core.publisher.FluxOnErrorReturn$ReturnSubscriber.onComplete(FluxOnErrorReturn.java:169) at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89) at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:152) at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onComplete(FluxOnAssembly.java:549) at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415) at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:446) at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:500) at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:800) at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1473) at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1336) at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1385) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: org.springframework.web.server.ResponseStatusException: 400 BAD_REQUEST at com.pangility.schwab.api.client.oauth2.SchwabOauth2Controller.lambda$processCode$7(SchwabOauth2Controller.java:306) at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.lambda$exchangeToMono$3(DefaultWebClient.java:413) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:132) at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113) at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854) at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)

sivadosapati commented 2 months ago

I'm happy to do a video meeting to walk you through what I was doing (if you have time available)

sivadosapati commented 2 months ago

@gporter0205 - Any insight would be greatly appreciated. I'm missing my automated trades which worked on TDA. Manually trading is a pain in the neck and I would love to jump on automatic trading sooner

sivadosapati commented 2 months ago

Any update will be greatly appreciated

gporter0205 commented 2 months ago

Sorry, Siva. Been pretty busy for the last week. I'll try take a look this week.

gporter0205 commented 1 month ago

Sorry again. I just had a chance to look through this. I think you are missing the connection between getting/generating the refresh token and then passing it to the services for use. Instead of just printing out the refresh and access tokens in the TokenHandler, save them so you can pass them to the init method of the api client so in the ScwabAccount class so it can use them when calling the API. You shouldn't need to call the /oauth2/schwab/code endpoint again like you did in step 3. Once you authorize the app at Schwab, it will call that endpoint with a code that it generates, which also triggers the TokenHandler methods. I hope this makes sense and helps.

sivadosapati commented 1 month ago

Thank you, Greg! I never saw onAccessToken() and onRefreshTokenChange() got called. I invoked http:/localhost:8080/oauth2/schwab/authorization?schwabUserId=<>&callback=https://127.0.0.1/ and expected to see these methods get triggered and wanted to see the output from those methods in the console but I didn't see any ouptut. If I saw the output, then I would have modified the code to save in a file and read them in init() method. When will these overridden methods get triggered()?

gporter0205 commented 1 month ago

They should get triggered by Schwab calling the oauth2/schwab/code end point after you authorize. Make sure the redirect url that you are using in your application.yml is in the list of redirect URLs that you have set up in your Schwab app on the developer portal and that your services are running on that host. The redirect url should be https://*DOMAIN*/oauth2/schwab/code where DOMAIN will be the host where your services are running. Schwab won't allow you to redirect to localhost:8080 and it'll require https. They will allow you to use https://127.0.0.1 though, but then you'll have to set up self signed certs on your development machine. It's a pain for development. I struggled with it for a while. Again, I hope this makes sense and helps.

ab101421 commented 4 weeks ago

@sivadosapati were you able to get past this issue?

I am also facing the same issue with step #9. I am using Greg's 'schwab-client-example' app. On invoking, http:/localhost:8080/oauth2/schwab/authorization?schwabUserId=<>&callback=https://127.0.0.1/oauth2/schwab/code , I get "There was an unexpected error (type=Not Implemented, status=501)." response from the app.

And, in the app logs, there is "c.p.s.a.c.oauth2.SchwabOauth2Controller : Initialization Error: Unable to get Refresh Token before service initialization" error message.

siva-dmg commented 3 weeks ago

@ab101421 -> I was busy with other things for the last few weeks and didn't get a chance to look at this. I will give this a try in the next few weeks and will update if I'm able to make further progress

ab101421 commented 3 weeks ago

@siva-dmg sounds good. thanks!

rajekra commented 3 weeks ago

Any fix available on above issue?