How would I save the "secret token" as a cookie before redirecting so that I can retrieve it later?
#Mustafa Simav's answer is correct. Everything strangely started working when I cleaned my project (deleted sbt generated files)
First of all, I don't now details of OAuth so I am not sure whether it is good idea to store token on cookie or not. However if you want to store and retrieve cookie with spray, you can do it like this:
val routes =
path("redirect") {
setCookies(HttpCookie("random_token", random_token)) {
redirect("twitter.com/authorize", Found)
}
} ~
path("get-cookie") {
parameters('token) { token =>
optionalCookie("random_token") {
case Some(random_token) if random_token == token => complete("OK")
case _ => complete("Error", BadRequest)
}
}
}
PS: Don't forget to concatenate routes with ~
Related
How to configure flask app with flask-jwt-extended for which we need something like below.
AccessToken/Bearer must sent as a Header (and not cookie)
RefreshToken must sent as httpOnlyCookie for /api/refreshtoken path only
How to set two different token one in header and one in cookie? We are able to set either both as cookie or both as a header.
Any help?
Thanks
Raxit
I wanted to do the same while building a React + Flask single page application after days of headache trying to understand authorization and authentication as I am a beginner.
Anyways, I managed to do it this way:
In Flask, config:
app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies']
app.config['JWT_REFRESH_COOKIE_PATH'] = '/auth/refresh'
And what I return in my login function:
resp = jsonify({'access_token': access_token})
set_refresh_cookies(resp, refresh_token)
return resp, 200
And in my refresh function:
# Refresh access token
#app.route('/auth/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
user = get_jwt_identity()
resp = {
'access_token': create_access_token(
identity={
'username': user['username'],
'role': user['role']
},
expires_delta=timedelta(seconds=600),
user_claims=user['role']
)
}
return jsonify(resp), 200
And on the front side, I collect the JSON access_token and set it in memory and use withCredentials to send the refresh_token with my API calls.
axios.defaults.withCredentials = true;
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
more precisely:
.then(({ data: { access_token } }) => {
axiosHttp.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
return jwt_decode(access_token);
})
then I use the data from my decoded access_token in a React Context Component to authenticate access to pages depending on roles.
logout is simply setting to null my context and calling the api to unset the refresh cookie
#app.route('/auth/logout', methods=['DELETE'])
#jwt_required
def logout():
resp = jsonify({"msg": "Successfully logged out"})
unset_jwt_cookies(resp)
return resp, 200
it's quite simple in the end but it took me quite a while to figure out!
I got strange issue. I'm implementing cart functionality on my website and I use session to store cart positions. I have a POST action to add new position to cart, and I have CSRF filter enabled to secure website. I call it with ajax on a product page, so first call is okay, but second says Unauthorized and in logs there are [CSRF] Check failed because no token found in headers for /cart. But it has. I call it with:
$("form").submit(function(e){
e.preventDefault();
$.ajax({
url: '/cart',
method: 'POST',
data: getCartPosition(),
beforeSend: function(xhr){xhr.setRequestHeader('Csrf-Token', $('input[name=csrfToken]').val());},
success: function (data, textStatus) {
alert('Added!');
},
error: function (error) {
alert('Error!');
}
});
});
and I put CSRF token in template somewhere:
#CSRF.formField
and it's in request:
I have enabled this in config
play.filters.csrf.bypassCorsTrustedOrigins=true
play.filters.hosts {
# Allow requests to example.com, its subdomains, and localhost:9000
allowed = ["localhost:9000", "localhost:4200"]
}
But what is strange that it seems it puts csrfToken in session, because after failed request I have session like this
Session(Map(cart -> {"positions":
[{"trackId":1},{"trackId":24},{"trackId":20}]},
username -> user,
token -> 0639d0b0-e7c8-4e82-9aad-2a43044e72db,
csrfToken -> e705413843ea96a6491a0e9e800ba36a712c4f70-1506542471068-0baeef7535eb9c889fb6fed2))
Idk why it's there, my add2cart action looks like:
private def cartAction(addToCartForm: Form[CartPosition], action: (Cart, CartPosition) => Cart)(implicit request: UserRequest[Any]) = {
addToCartForm.fold(
_ => BadRequest("Error!"),
position => {
getCart match {
case Some(cart) => Ok("Ok").withSession("cart" -> Json.toJson(action(cart, position)).toString(), "username" -> request.session.get("username").getOrElse(""), "token" -> request.session.get("token").getOrElse(""))
case _ => Ok("Ok, no").withSession("cart" -> Json.toJson(action(Cart(Seq.empty), position)).toString())
}
}
)
}
def addToCart() = guestAction { implicit request =>
cartAction(addToCartForm.bindFromRequest, addCartPos)
}
and addCartPos just adds position to json
I've got same issue with Play 2.7.3.
In my case, the form is generated by the Twirl with the csrf token and because I'm using ajax to submit the form, I've copied the csrf token from the rendered form and pass it to the ajax header as writen in the Play's documentation.
The form can be submitted multiple times so I need to updated the token. Therefore I'm passing through ajax response new csrf token taken in the controller from play.filters.csrf.CSRF.getToken.get.value.
But unfortunatelly, the second submission failed as cutoffurmind mentioned.
And the fix is as described by Knut Arne Vedaa to add new token to the session. I did it by the withSession method.
So the controller response is looking like this:
Ok(Json.obj(
"status" -> (user != None),
"notif" -> "Success login",
"data" -> Map(
"adminUrl" -> "www.something ...",
"csrf" -> play.filters.csrf.CSRF.getToken.get.value
)
)).withSession(
"uid" -> user.getOrElse(User()).id.toString,
"csrfToken" -> play.filters.csrf.CSRF.getToken.get.value
)
It doesn't look like an issue as Play Framework doesn't have session data kept on the server, so it is logical that the token has to be updated in the client site after the ajax request. The main issue is that it is not mentioned in the documentation (in the CSRF ajax section), what could be handy as people simply doesn't read the doc from A to Z in expected order.
In my case the solution was to set the play.filters.csrf.cookie.name config option to a value other than null:
play.filters.csrf.cookie.name = csrf_token
I've used as example play-silhouette-angular-seed.
Authorization via Satellizer works fine.
When I try to authorize via iOs app I got next error:
com.mohiva.play.silhouette.impl.exceptions.UnexpectedResponseException:
[Silhouette][facebook] Cannot build OAuth2Info because of invalid response format:
List((/access_token,List(ValidationError(List(error.path.missing),WrappedArray()))))
I got an error 400 in this function from OAuth2Provider.scala :
protected def getAccessToken(code: String)(implicit request: RequestHeader): Future[OAuth2Info] = {
httpLayer.url(settings.accessTokenURL).withHeaders(headers: _*).post(Map(
ClientID -> Seq(settings.clientID),
ClientSecret -> Seq(settings.clientSecret),
GrantType -> Seq(AuthorizationCode),
Code -> Seq(code),
RedirectURI -> Seq(resolveCallbackURL(settings.redirectURL))) ++ settings.accessTokenParams.mapValues(Seq(_))).flatMap { response =>
logger.debug("[Silhouette][%s] Access token response: [%s]".format(id, response.body))
Future.from(buildInfo(response))
}
}
This error has been risen because Satellizer for authentication via Facebook send to server an 'authentication code' and Silhouette server use this code to get Facebook 'access token' and create user.
Facebook iOs SDK, instead, obtained 'Access token' and I've tried to send it to server in Json in field 'code' like 'Satellizer.
To resolve this issue I send an 'access token' in Json field named 'access_token' and use next code to authenticate mobile application:
class MobileSocialAuthController #Inject() (
val messagesApi: MessagesApi,
userService: UserService,
authInfoRepository: AuthInfoRepository,
socialProviderRegistry: SocialProviderRegistry,
val env: Environment[User, JWTAuthenticator])
extends Silhouette[User, JWTAuthenticator]
{
def authenticate(provider: String) = UserAwareAction.async(parse.json) {
implicit request =>
provider match {
case "facebook" =>
request.body.asOpt[OAuth2Info] match {
case Some(authInfo) =>
(socialProviderRegistry.get[FacebookProvider](provider) match {
case Some(p: FacebookProvider) =>
for {
profile <-p.retrieveProfile(authInfo)
user <- userService.save(profile)
authInfo <- authInfoRepository.save(profile.loginInfo, authInfo)
authenticator <- env.authenticatorService.create(profile.loginInfo)
token <- env.authenticatorService.init(authenticator)
} yield {
env.eventBus.publish(LoginEvent(user, request, request2Messages))
Ok(Json.obj("token" -> token))
}
case _ => Future.failed(new ProviderException(s"Cannot authenticate with unexpected social provider $provider"))
}).recover {
case e: ProviderException =>
logger.error("Unexpected provider error", e)
Unauthorized(Json.obj("message" -> Messages("could.not.authenticate")))
}
case _ =>
Future(BadRequest(Json.obj(
"message" -> "Bad OAuth2 json.")))
}
case _ =>
Future(BadRequest(Json.obj(
"message" -> "You can use only Facebook account for authentication.")))
}
}
}
As a result, I have a token which I use in ios application to obtain resources.
This happens when the OAuth2Provider gets a response it can't parse, which is, any non-success response. So there can be many reasons for this error, for instance the authorization code is invalid or expired, or you haven't configured the redirect_uri properly (check your Facebook app configuration on the Facebook dev site to set the redirect_uri).
Silhouette does log the response it gets from Facebook which should help you debug what the actual issue is, the log line to look for is in the snippet you provided:
logger.debug("[Silhouette][%s] Access token response:...
So check your logs, there you should see the response from Facebook, likely with an error indicating why they couldn't give you an access_token.
I use auth login (out of the box). I have want to add Facebook login.
I have installed Socialize package, and In Auth/AuthController I added method fb:
public function fb()
{
return \Socialize::with('facebook')->redirect();
}
When I call http://ip/auth/fb its redirect me to http://ip/auth/login#=
Please help
First you need to create the FB project and you will have the client_id (App ID) and secret_key (App secret)
In your services configuration file: config/services.php you need to specify the facebook key like this:
'facebook' => [
'client_id' => 'client_id from fb',
'client_secret' => 'secret_key from fb',
'redirect' => 'http://your_site/your_fb_login_ok_path',
],
then you create the route:
Route::get('your_fb_login_ok_path', function ($facebook = "facebook")
{
// Get the provider instance
$provider = Socialize::with($facebook);
// Check, if the user authorised previously.
// If so, get the User instance with all data,
// else redirect to the provider auth screen.
if (Input::has('code'))
{
$user = $provider->user();
return var_dump($user);
} else {
return $provider->redirect();
}
});
This should do it.
Then remember to add this URL to your facebook redirect: http://your_site/your_fb_login_ok_path
That is the URL that FB will redirect you to after successful login.
There are a number of possibilities
You may have set the routes wrong
The Facebook Redirect Url you specified might be wrong
I keep getting 403 from Intuit AggCat API in response to all requests except getInstitutions and getInstitutionDetails (they return correct data). Has anyone else experienced that?
Ruby code excerpt:
IntuitIdsAggcat.config(:issuer_id => "...")
IntuitIdsAggcat.config(:oauth_consumer_key => "...")
IntuitIdsAggcat.config(:oauth_consumer_secret => "...")
IntuitIdsAggcat.config(:certificate_path => "...")
IntuitIdsAggcat::Client::Services.delete_customer '1'
IntuitIdsAggcat::Client::Services.discover_and_add_accounts_with_credentials 14007, 1, { "onlineID" => "...", "passcode" => "..." }
IntuitIdsAggcat::Client::Services.get_customer_accounts 1
Response:
{:challenge_session_id=>nil, :challenge_node_id=>nil, :response_code=>"403", :response_xml=><UNDEFINED> ... </>}`
I am using https://github.com/rewardsummit/intuit_ids_aggcat
The documentation for that response code is located [here]: https://ipp.developer.intuit.com/index.php?title=0010_Intuit_Partner_Platform/0020_Aggregation_%26_Categorization_Apps/AggCat_API/0700_Error_Codes
It looks like the users credentials either changed or they are locked.
regards,
Jarred