How can I change the bearer token in Moya - swift

the documentation shows how to make targets require bearer tokens, which I did like this
extension MyService: AccessTokenAuthorizable {
var authorizationType: AuthorizationType {
switch self {
case .resetPassword, .postTextBook, .bookmarkBook, .getBookmarks, .logout, .verify:
return .bearer
default:
return .none
}
}
}
then it shows how to add tokens to the providers, which I did like this
let token = "abc123"
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<MyService>(plugins: [authPlugin])
but when the token expires, how can I change the token? and does Moya offer a way to automate this process, where if I get a forbidden http response (meaning I am not authorized), it automatically requests a token?

The implementation details of authentication/authorization can be quite different for each API out there. This is the reason why Moya will not handle the auth for you.
That said, implementing your own authentication/authorization can be done in many ways. It will depend on your constraints and/or preferences. As of today, you can find a few solutions sparsely outlined in Moya documentation:
Use the PluginType to add your auth to the requests. But think that this can potentially be used to refresh the token if needed. You may also need to intercept the completion of the request to detect authorization errors and apply your preferred recovery scenario (eg. refresh the token and retry the call).
Same can be implemented using the endpointClosure and/or requestClosure.
You can also consider implementing Alamofire's RequestAdapter and RequestRetrier. Depending on your needs, this can make retries easier. However, on them you will not have straightforward access to your TargetType, so you may need to find a way to recognize the different auth methods needed (ie. your bearer or none).
A few direct references to their documentation:
Plugins
Endpoints
Authentication
Alamofire Automatic Validation
Also, I highly encourage anybody to learn/get inspiration from Eilodon's Networking source code.

for change/refresh token i used this
static func send(request: TargetType) -> PrimitiveSequence<SingleTrait, Response> {
return provider.rx.request(request)
.retry(1)
.observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
.filterSuccessfulStatusAndRedirectCodes()
.retryWhen({ (errorObservable: Observable<Error>) in
errorObservable.flatMap({ (error) -> Single<String> in
if let moyaError: MoyaError = error as? MoyaError, let response: Response = moyaError.response {
if **check forbidden http responses here** {
return provider.rx.request(.refreshToken(*your refresh token here*))
.filterSuccessfulStatusCodes()
.mapString(atKeyPath: "*json path to new access token*")
.catchError { (_) in
logout()
throw error
}
.flatMap({ (newAccessToken) -> PrimitiveSequence<SingleTrait, String> in
changeAccessToken()
return Single.just(newAccessToken)
})
}
}
throw error
})
})
}
static func logout() {
// logout action
}
static func changeAccessToken() {
// set new access token
}

Related

Vapor 4 Authentication Issue [User is not authenticated]

I have an iOS client with OAuth 2.0 as an authenticating mechanism.
When a user signs in, I authenticate him with this method (Google sign in for example):
func processGoogleLogin(request: Request, token: String) throws -> EventLoopFuture<ResponseEncodable> {
try Google
.getUser(on: request)
.flatMap { userInfo in
User
.query(on: request.db)
.filter(\.$email == userInfo.email)
.first()
.flatMap { foundUser in
guard let existingUser = foundUser else {
//creating a new user
return user
.save(on: request.db)
.map {
request.session.authenticate(user)
//redirecting with info
}
}
request.session.authenticate(existingUser)
//redirecting with info
}
}
}
After the login, I want to check if the user is authenticated and if I've successfully authenticated the user.
So I have an endpoint that I protect from unauthenticated users, but even after signing in, the user cannot access this endpoint as he is not authenticated.
Error:
{
"error": true,
"reason": "User not authenticated."
}
My User Model conforms to ModelSessionAuthenticatable.
I also use the SessionMiddleware (ImpreialController is the auth controller):
let imperialController = ImperialController(sessionsMiddleware: app.sessions.middleware)
app.middleware.use(app.sessions.middleware)
In ImperialController:
class ImperialController {
private let sessionsMiddleware: Middleware
init(sessionsMiddleware: Middleware) {
self.sessionsMiddleware = sessionsMiddleware
}
....
And finally the protected route:
let protected = app.grouped(User.guardMiddleware())
protected.get { req -> HTTPResponseStatus in
return .ok
}
It may be that you are doing it, but just not showing it in your question. You need to create and register an instance of SessionsMiddleware using something like this:
app.middleware.use(SessionsMiddleware(session: MemorySessions(storage: MemorySessions.Storage())))
Do this before you create the instance of your controller.
EDIT in reply to OP' comment:
I normally pass the instances of the different middleware explicitly because I tend to apply subsets to groups of routes rather than all the middleware. For example:
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.middleware.use(SessionsMiddleware(session: MemorySessions(storage: MemorySessions.Storage())))
app.middleware.use(User.sessionAuthenticator(.mysql))
try app.register(collection: APIController(middleware: UserToken.authenticator()))
var middleware: [Middleware] = [CustomMiddleware.InternalErrorMiddleware()]
try app.register(collection: InsecureController(middleware: middleware))
middleware.append(contentsOf: [User.redirectMiddleware(path: [C.URI.Solidus].string),
User.authenticator(), User.guardMiddleware(),
CustomMiddleware.SessionTimeoutMiddleware()])
try app.register(collection: CustomerController(middleware: middleware))
BTW, have you included my line 3 above? That may be your problem.

Vapor: sending post requests

I am trying to send an HTTP request using Vapor, to verify a recaptcha
Google's Captcha api is defined as follows:
URL: https://www.google.com/recaptcha/api/siteverify METHOD: POST
POST Parameter
Description
secret
Required. The shared key between your site and reCAPTCHA.
response
 Required. The user response token provided by the reCAPTCHA client-side integration on your site. 
remoteip
Optional. The user's IP address. 
So I need to make a POST request with 2 parameters (secret and response).
In Swift i have:
func routes(_ app: Application throws {
app.on(.POST, "website_form") { req -> EventLoopFuture<View> in
var form: FromRequest = /*initial values*/
/*decode form data*/
do {
req.client.post("https://www.google.com/recaptcha/api/siteverify") { auth_req in
try auth_req.content.encode(CaptchaRequestBody(secret: "6Lfoo98dAAAAALRlUoTH9LhZukUHRPzO__2L0k3y", response: form.recaptcha_response), as: .formData)
auth_req.headers = ["Content-Type": "application/x-www-form-urlencoded"]
}.whenSuccess { resp_val in
print("Response: \(resp_val)")
}
}
}
/* More code */
}
struct CaptchaRequestBody: Content {
let secret: String
let response: String
}
After running the post request, I get following error code:
{
"success": false,
"error-codes": [
"missing-input-secret"
]
}
I can not find any solution that works, even the official Vapor docs were of no use, could someone please help me?
The Google API requires requests to be URL-encoded forms. By using the .formData setting, you are enforcing MultiPart.
Change the setting to .urlEncodedForm, which ensures the request conforms to the Google API requirement.
As Nick stated: the problem was that instead of .formData, I needed to use .urlEncodedForm.

How to decorate Siesta request with an asynchronous task

What is the correct way to alter a Request performing an asynchronous task before the Request happens?
So any request Rn need to become transparently Tn then Rn.
A little of background here: The Task is a 3rd party SDK that dispatch a Token I need to use as Header for the original request.
My idea is to decorate the Rn, but in doing this I need to convert my Tn task into a Siesta Request I can chain then.
So I wrapped the Asynchronous Task and chained to my original request.
Thus any Rn will turn into Tn.chained { .passTo(Rn) }
In that way, this new behaviour is entirely transparent for the whole application.
The problem
Doing this my code end up crashing in a Siesta internal precondition:
precondition(completedValue == nil, "notifyOfCompletion() already called")
In my custom AsyncTaskRequest I collect the callbacks for success, failure, progress etc, in order to trigger them on the main queue when the SDK deliver the Token.
I noticed that removing all the stored callback once they are executed, the crash disappear, but honestly I didn't found the reason why.
I hope there are enough informations for some hints or suggests.
Thank you in advance.
Yes, implementing Siesta’s Request interface is no picnic. Others have had exactly the same problem — and luckily Siesta version 1.4 includes a solution.
Documentation for the new feature is still thin. To use the new API, you’ll implement the new RequestDelegate protocol, and pass your implementation to Resource.prepareRequest(using:). That will return a request that you can use in a standard Siesta request chain. The result will look something like this (WARNING – untested code):
struct MyTokenHandlerThingy: RequestDelegate {
// 3rd party SDK glue goes here
}
...
service.configure(…) {
if let authToken = self.authToken {
$0.headers["X-Auth-Token"] = authToken // authToken is an instance var or something
}
$0.decorateRequests {
self.refreshTokenOnAuthFailure(request: $1)
}
}
func refreshTokenOnAuthFailure(request: Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response, // Did request fail…
error.httpStatusCode == 401 else { // …because of expired token?
return .useThisResponse // If not, use the response we got.
}
return .passTo(
self.refreshAuthToken().chained { // If so, first request a new token, then:
if case .failure = $0.response { // If token request failed…
return .useThisResponse // …report that error.
} else {
return .passTo(request.repeated()) // We have a new token! Repeat the original request.
}
}
)
}
}
func refreshAuthToken() -> Request {
return Request.prepareRequest(using: MyTokenHandlerThingy())
.onSuccess {
self.authToken = $0.jsonDict["token"] as? String // Store the new token, then…
self.invalidateConfiguration() // …make future requests use it
}
}
}
To understand how to implement RequestDelegate, you best bet for now is to look at the new API docs directly in the code.
Since this is a brand new feature not yet released, I’d greatly appreciate a report on how it works for you and any troubles you encounter.

Removing "Bearer" from token header without implementing a custom authentication scheme or parsing the token?

Currently utilizing a JWT authentication schema where the tokens have "Bearer: in the schema. Is it possible to remove the "Bearer" prefix so I wouldn't need to add it on the client side just to parse it out on the backend again? Is there a way to do this without implementing a custom scheme (So while still using the Bearer scheme) AND without having to parse the actual token for the "Bearer: " text?
Right now, the code looks like:
var token = req.headers.authorization;
var newToken = token.replace("Bearer ", "");
jwt.verify(newToken, jwtSecret, function (err, success) {
if (err) {
return res.
status(401).
end('Unauthorized, invalidtoken');
} else {
return next();
}
})
Ideally it would be implemented as such:
var token = req.headers.authorization;
jwt.verify(token, jwtSecret, function (err, success) {
if (err) {
return res.
status(401).
end('Unauthorized, invalidtoken');
} else {
return next();
}
})
Would this be okay? What are the implications of removing "Bearer" from the jwt authorization headers ?
Thanks
There is no programmatic difference from removing Bearer token in formatting the request header. If you do choose to do so, you are violating RFC and HTTP standards. It would be like sending a payload in a GET response and saving data to the database.
Use of bearer tokens derived from the Oauth design so have a look at here for standards.

Spray Authentication method with BasicAuth

Spray is hard!! I now know that my knowledge on HTTP protocol is not nearly enough and API design isn't easy. However, I still very much want my practice app to work. I'm writing this authentication for POST/PUT/DELETE method. It appears that there are at least two ways to do this: BasicAuth or write a custom directive.
I found this article:
BasicAuth: https://github.com/jacobus/s4/blob/master/src/main/scala/s4/rest/S4Service.scala
I'm trying it out because it looks simple.
The compile and run stages are fine, and the server runs. However, when I'm trying to send a PUT request to test the implementation (using Python's Httpie: http PUT 127.0.0.1:8080/sec-company/casper username=username token=123), the feedback is:HTTP/1.1 404 Not Found
Here's my route:
pathPrefix("sec-company") {
path("casper") {
//first examine username and token
authenticate(BasicAuth(CustomUserPassAuthenticator, "company-security")) {userProfile =>
post { ctx =>
entity(as[Company.Company]) { company =>
complete {
company
}
}
}
}
Here is my implementation of UserPassAuthenticator:
object CustomUserPassAuthenticator extends UserPassAuthenticator[UserProfile] {
def apply(userPass: Option[UserPass]) = Promise.successful(
userPass match {
case Some(UserPass(user, token)) => getUserProfile(user, token)
case _ => None
}
).future
}
First of all, is this the right way to implement authentication? Second, where does UserPassAuthenticator find the username and password?? Can I send back a better HTTP header other than 404 to indicate failed authentication?
If this is far from correct, is there any tutorial on authentication that I can follow? TypeSafe's Spray templates are more about overall patterns and less about Spray's functionality!
Thank you!
I had the same problem, even after looking at https://github.com/spray/spray/wiki/Authentication-Authorization (which says it's for an older version of Akka but it still seems to apply) I came up with the following:
trait Authenticator {
def basicUserAuthenticator(implicit ec: ExecutionContext): AuthMagnet[AuthInfo] = {
def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
for {
p <- userPass
user <- Repository.apiUsers(p.user)
if user.passwordMatches(p.pass)
} yield AuthInfo(user)
}
def authenticator(userPass: Option[UserPass]): Future[Option[AuthInfo]] = Future { validateUser(userPass) }
BasicAuth(authenticator _, realm = "Private API")
}
}
I mix in this trait into the Actor that runs the routes and then I call it like this:
runRoute(
pathPrefix("api") {
authenticate(basicUserAuthenticator) { authInfo =>
path("private") {
get {
authorize(authInfo.hasPermission("get") {
// ... and so on and so forth
}
}
}
}
}
}
The AuthInfo object returned by the validateUser method is passed as a parameter to the closure given to the authorize method. Here it is:
case class AuthInfo(user: ApiUser) {
def hasPermission(permission: String) = user.hasPermission(permission)
}
In Spray (and HTTP), authentication (determining whether you have a valid user) is separate from authorization (determining whether the user has access to a resource). In the ApiUser class I also store the set of permissions the user has. This is a simplified version, my hasPermission method is a bit more complex since I also parametrize permissions, so it's not just that a particular user has permission to do a get on a resource, he might have permission to read only some parts of that resource. You might make things very simple (any logged-in user can access any resource) or extremely complex, depending on your needs.
As to your question, when using HTTP BASIC authentication (the BasicAuth object), the credentials are passed in the request in an Authorization: header. Your HTTP library should take care of generating that for you. According to the HTTP standard, the server should return a 401 status code if the authentication was incorrect or not provided, or a 403 status code if the authentication was correct but the user doesn't have permissions to view the content.