Hi,
I am trying to use the AccessTokenPlugin functionality and i have some issue with it. I need to find a way to initiate the AccessTokenPlugin only after my users are logged in and they have a token.
Currently in my Moya implementation I am creating the provider in the following way:
let token = ""
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let myProvider = MoyaProvider<API>(plugins: [authPlugin])
enum API {
case loginUser(username: String, password: String)
case createUser(user: User)
}
extension API : TargetType, AccessTokenAuthorizable {
...
I need the JWT bearer to all the requests except the login and the creation of new users.
The problem is that i need to find a way to set the token only after the users are logged in. I was thinking about creating a new provider and override the old one but i don't understand if this is the recommended way to go with.
Can you please advice?
Thanks.
Hey @ranhsd. Right now to add a token you need to specify when/how you want to add it (by implementing AccessTokenAuthorizable) and what you want to add (by using tokenClosure). The first one should give you the ability to manipulate when/how you want to add it. You can have a default extension that you would always want to add the token, but in your login/register endpoint you would not send it with the request, e.g.:
extension API: AccessTokenAuthorizable {
var authorizationType: AuthorizationType {
switch self {
case .loginUser, .createUser: .none
default: return .bearer
}
}
}
Let me know if it's what you are looking for.
Hi @sunshinejr
Thanks for your quick response.
It鈥檚 really what I meant. I need to know when I can set the plugin to the provider. Because the first time that I create the provider and the user is not logged in I don鈥檛 really have the token I need to set the plugin on the provider only when I really have it (after user logged in and access token returns from the server) but I already initiate the provider before with the plugin. I need to find a way to recreate the provider again but this time to send the access token to the plugin
If you need more info please let me know
Thanks,
Ran
Oh, I think I understand the issue now. We had this problem before, so we changed the plugin lately that you don't have to specify token when registering the provider. Now we have a tokenClosure, instead of a token, so you can specify a way of getting your token instead of a token itself. We also added a convenience autoclosure keyword, so even a string is accepted by this plugin (thus why the code you posted works as well).
So, let's say that you save your token in a keychain. You can initialize a provider even though you don't have your token yet:
let tokenClosure: () -> String = {
return Keychain.getToken() // here you can specify how to retrieve a token
}
let authPlugin = AccessTokenPlugin(tokenClosure: tokenClosure)
let myProvider = MoyaProvider<API>(plugins: [authPlugin])
Does this resolve your problem now, @ranhsd?
Hi @sunshinejr
it looks better. The question is if the tokenClosure will be executed in each api call ?
Thanks!
Ran.
Yes, @ranhsd. Each time there is a request, the plugin evaluates the tokenClosure just before, as you can see here. No caching involved.
@sunshinejr Thank you very much!!
Glad it helped! 馃帀
@sunshinejr authPlugin doesn't seem to work when using <MultiTarget> like below. Is there a way to implement that? Seems strange because NetworkLoggerPlugin works fine using <MultiTarget>
let tokenClosure: () -> String = {
return Keychain.getToken() // here you can specify how to retrieve a token
}
let authPlugin = AccessTokenPlugin(tokenClosure: tokenClosure)
let provider = MoyaProvider<MultiTarget>(plugins: [authPlugin])
@sunshinejr Why tokenClosure, not tokenClosure: (TargetType) -> String
I have two 2+ accessTokens for different endPoints and 3 types:
basic, bearer and custom - "token"
I think AccessTokenPlugin good only for static accessToken.
public enum CustomAthorizationType {
case none
case basic// = "Basic"
case bearer// = "Bearer"
case custom(String)
var value: String {
switch self {
case .none: return ""
case .basic, .bearer: return String(describing: self).capitalized
case .custom(let custom): return custom
}
}
}
static var plugins: [PluginType] {
return [
CustomAccessTokenPlugin(tokenClosure: { target -> String in
switch target {
case Target.request(let data):
guard
let bytes = try? HMAC(key: encryptingKey, variant: .sha1).authenticate(data.bytes),
let encrypted = bytes.toBase64() else { return "" }
return encrypted
case Target.login:
guard let loginData = String(format: "%@:%@", appClientUsername, appClientPassword).data(using: .utf8)
else { return "" }
return loginData.base64EncodedString()
default: return ""
}
}),
NetworkLoggerPlugin(verbose: true, cURL: true)]
}
Is the encapsulation broken in this way?
I can try to extend the plugin and make a pull request.
@SeRG1k17 I think you can use two different providers, each with a different plugin for your request. We have an open PR for updating the AccessTokenPlugin. Maybe you would like to review the PR and provide any input there
Sent with GitHawk
@kavanbrandon Hey, my guess would be that MultiTarget doesn't conform to AccessTokenAuthorizable and you would have to do it yourself:
extension MultiTarget: AccessTokenAuthorizable {
var authorizationType: AuthorizationType {
// here you will have to check whether the `target` is also conforming to `AccessTokenAuthorizable` or not...
}
}
@SeRG1k17 Hmm, using TargetType as parameter to tokenClosure seems fine with me, as @SD10 it would be awesome if you would provide your feedback in the PR that improves this plugin (#1521). Current workaround would be to use multiple providers for multiple access tokens.
@sunshinejr Thanks!
@sunshinejr @SD10 I do not really want to create two separate providers. Without this, there will be a violation of code cleanliness?
Also i have two different baseURL, and it`s my solution:
//EndPoints.swift
enum BaseURL {
case azure(EndPoints)
case cloudapp(EndPoints)
static func type(for endpoint: EndPoints) -> BaseURL {
switch endpoint {
case .request: return .cloudapp(endpoint)
case .login: return .azure(endpoint)
}
}
var url: URL {
var url: String!
switch self {
case .azure: url = "https://"
case .cloudapp: url = "http://t"
}
return URL(string: url)!
}
}
public var urlType: BaseURL { return BaseURL.type(for: self) }
public var baseURL: URL { return urlType.url }
//...
It`s possible create this as generic.
I think we can make this plugin more convenient, so that the headers are not static.
@sunshinejr
Hi, it is having an error when I follow your sample code.
_Xcode Error: "Function produces expected type 'String'; did you mean to call it with '()'?"_
Could you advise this issue, please?
let tokenClosure: () -> String = {
return Keychain.getToken()
}
let authPlugin = AccessTokenPlugin(tokenClosure: tokenClosure)
Xcode version: 9.4.1
@sunshinejr
I finally solved this issue by using code below, hope it could help who are facing the same
var tokenClosure: String {
return Keychain.getToken()
}
let authPlugin = AccessTokenPlugin(tokenClosure: tokenClosure)
Most helpful comment
Oh, I think I understand the issue now. We had this problem before, so we changed the plugin lately that you don't have to specify token when registering the provider. Now we have a
tokenClosure, instead of atoken, so you can specify a way of getting your token instead of a token itself. We also added a convenienceautoclosurekeyword, so even a string is accepted by this plugin (thus why the code you posted works as well).So, let's say that you save your token in a keychain. You can initialize a provider even though you don't have your token yet:
Does this resolve your problem now, @ranhsd?